您的位置 首页 java

Java四则运算的那些坑-加减乘除避坑指南

Java四则运算的那些坑-加减乘除避坑指南

使用 Java 开发多年,但是被Java狂虐却从来都没变过,而且任何一个Java的小角落,都能把我虐的体无完肤,但是无奈要靠Java吃饭,还得恬着脸继续使用下去。

最近开发的项目中涉及到了大量的资金计算,资金计算对数字要求的比较严谨,作为一个粗心而又自大的Java程序员,一直没把这个当回事儿,于是又被Java吊打一遍。

下面记录一下Java中四则运算的一些需要注意的小坑,加减乘除都有坑噢,一不小心就进坑!

数学计算,免不了要想到 int long double 这种数据类型,但double自古在程序界就特立独行,表面上看像数学中学到的小数,实际上完全两码事儿。

下面开始趟坑:

Double的坑

坑1:加法坑

 double d1 = 0.1;
d1 += 0.1;
d1 += 0.1;
d1 += 0.1;
d1 += 0.1;
d1 += 0.1;
d1 += 0.1;
d1 += 0.1;
d1 += 0.1; 
// 连着加了8次
System.out.println(d1);  
  

不是我们 期望 的0.9哦

坑2:减法坑

 double d1 = 1.0;
double d2 = 0.1;
d1 -= d2;
d1 -= d2;
d1 -= d2;
d1 -= d2;
d1 -= d2;
d1 -= d2;
d1 -= d2;
d1 -= d2;
d1 -= d2;
// 连着减了9次0.1
System.out.println(d1);  
  

0.10000000000000014

不是我们期望的0.1哦

坑3:乘法坑

 double d1 = 123213.0;
d1 *= 0.35;
System.out.println(d1);
  

43124.549999999996

不是我们期望的43124.55哦

坑4:除法坑

 double result = 11.4/12;
System.out.println(result);  
  

0.9500000000000001

不是我们期望的0.95哦

由此可见,如果需要对资金进行加减乘除四则运算,千万不能使用double类型的,结果总是出你意料。

坑5:大数转为 字符串

再试个别的,数学运算中总会使用比较大的数字吧,试一个上千万的数字吧:

 double bigValue = 12345678.9;
System.out.println(bigValue);    

1.23456789E7

竟然变成科学计数法了… … 这不是给人看的格式啊。 所以,如果你想要显示大的浮点数,请使用DecimalFormat自己来格式化。

坑6:除0坑

从小学数学就知道不能除以0,看看double的表现:

 result = 1.0/0.0;
System.out.println(result);
result = 0.0/0.0;
System.out.println(result);    

Infinity

NaN

确实不能除以0,但是结果出乎你的意料之外吧,没错,Double提供了几个方法来检查:

 Double.isInfinite(double) // 是否是Infinite数 

Double.isFinite(double) // 是否有有限的数 

Double.isNaN(double) // 是否是这个数  

NaN = Not -a-Number ,两个NaN的数一比较,竟然不相等,是不是越来越有意思了。

坑7:比较是否相等的坑

 System.out.println(1.1 == 1.1? "true": " false ");
System.out.println(1.1 == 1.100000000000001? "true": "false");
System.out.println(1.1 == 1.10000000000000009? "true": "false");    

true

false

true

又出乎意料了

总结: 在进行资金相关的运算时,千万不能使用Double类型,否则一个不小心就让你好看。

那么不使用double,使用哪个类型合适呢?Java提供了一个叫 BigDecimal 的对象,专门用来做计算使用,但是BigDecimal也不是那么容易驾驭的,使用不当,还是会被爆出翔。

附:阮一峰的博客:浮点数在计算机中的表示方法,提示Double为何如此诡异。

链接如下:

BigDecimal的坑

坑1: 实例化 对象

 BigDecimal num = new BigDecimal(0.3);
System.out.println(num);
  

0.299999999999999988897769753748434595763683319091796875

这不是玩人嘛,换一种方法:

 BigDecimal num = new BigDecimal("0.3");
System.out.println(num);
  

0.3

这总算可以了。

坑2:除法

 BigDecimal d = BigDecimal.ONE;  
d.divide(new BigDecimal("3"));  
  

Exception in thread “main” java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

这是什么鬼?为毛报个异常啊… 好吧,保留两位小数试试

 BigDecimal d = BigDecimal.ONE;  
d.divide(new BigDecimal("3"), 2, BigDecimal.ROUND_HALF_UP);
System.out.println(d);
  

1

咦,为何不是0.33,数没变呢? 原因是:BigDecimal是不可变的,每次运算后返回新的对象,再改:

 BigDecimal d = BigDecimal.ONE;  
d = d.divide(new BigDecimal("3"), 2, BigDecimal.ROUND_HALF_UP);
System.out.println(d);
  

0.33

长出一口气,总算可以了。

坑3:小数部分舍入进位的坑

四舍五入,有啥可说的?还真有得说,舍入进位可以参看RoundingMode枚举,里面包含各种舍入进位,需要仔细研读哦,否则一脚一个坑。 这里要说的不是舍入进位的类型,而是说BigDecimal中提供的方法:

 num.setScale(2, BigDecimal.ROUND_HALF_EVEN); 

num.setScale(2, RoundingMode.HALF_EVEN);   

这里面 BigDecimal.ROUND_HALF_EVEN RoungingMode.HALF_EVEN 这两个东西有啥区别?

答案是没有区别,一样使用,RoungingMode 枚举是JDK1.5以后,Java支持枚举类型后加的,里面有一个oldMode,保存的就是BigDecimal中对应的常量。

坑4:保留位数的坑

将小数部分保留N位这是一个非常常用的操作,以前使用double时,都是采用:

  1. 乘10的N次方
  2. round
  3. 除以10的N次方 这种方法来算,牛逼闪闪的BigDecimal必然会提供舍入方法,而且使用起来很简单:
 BigDecimal num = new BigDecimal("1.2345678"); 
num = num.setScale(2, BigDecimal.ROUND_HALF_EVEN); 
System.out.println(num);  

1.23

事情本来很容易结束了,但是眼睛贱又扫了一眼别的地方,MathContext,这是啥?好像也是用来舍入的,试试:

 MathContext ctx = new MathContext(2);
num = new BigDecimal("12.34");
num1 = new BigDecimal("56.78");
num = num.multiply(num1, ctx);  // 12.34*56.78 = 700.6652
System.out.println(num.toString()); 
  

7.0E+2

不是我们想看到700.67 呀,改一下

 MathContext ctx = new MathContext(3);  

701

 MathContext ctx = new MathContext(4);  

700.7

 MathContext ctx = new MathContext(5);  

700.67

 MathContext ctx = new MathContext(6);  

700.665

明白了,这个数字是 所有数字的位数,而不是小数点后的位数

坑5:比较相等的坑

 num = new BigDecimal("1");
num1 = new BigDecimal("1.0");
System.out.println(num.equals(num1)); // 竟然不相等呀
System.out.println(num.compareTo(num1)); // 0     

false 0

BigDecimal的equals大大出乎意料,可不能随便使用equals方法来比较两个BigDecimal的大小了。还好compareTo还是好用的… …

总结

以上所说的坑,其实在文档中都有所述, 但是我们开发时,更多时会依赖以往的经验,这种经验会导致踩到坑里,有时也会踩到屎坑里 ,所谓细节决定成败,细节决定生死,还是要时刻提醒自己不要大意,注意细节。

最后总结一下: Double在运算时就放弃吧,不要使用了。保存个临时数据时使用还没啥问题,至少我没遇到过。

BigDecimal要注意:

 1.实例化时使用字符串
2.一定要使用操作后返回值(BigDecimal是不可变的)
3.进行运算前先设置保留几位小数
4.除法运算要在计算时设置保留位数
5.确定要使用哪种进位(四舍五入,还是银行家舍入法)
6.MatchContext不要随便用,根据需要来使用
7.比较两个Decimal是否相等,使用compareTo,不要使用equals  

总的来说就是:数学运算需要严谨,Java为了严谨,提供了非常复杂而又麻烦的使用方法,我们还是咬牙凑合用吧。

文章来源:智云一二三科技

文章标题:Java四则运算的那些坑-加减乘除避坑指南

文章地址:https://www.zhihuclub.com/177806.shtml

关于作者: 智云科技

热门文章

网站地图