读者在日常业务开发中或多或少会涉及到一些数值计算逻辑,尤其是金融行业需要特别严谨,通常由数值引起的问题都是潜移默化的且难以发现,拿“数值偏差”来说,初期的微小偏差是很难察觉的,当这种偏差累积成量级的数值错误而引起生产事故则为时已晚。
本篇就针对这一部分踩过的坑为读者做科普和预警。
1
浮点数的精度偏差:1减0.8真的等于0.2吗?
测试一下:
System.out.println(1 - 0.8);
控制台输出:
0.19999999999999996
显然结果并非我们所想,原因是十进制数在计算机底层是以二进制数保存的(0、1),浮点数的精度偏差就产生于这个进制转换过程。
既然浮点数会产生精度偏差,那么使用Java提供的精度运算类BigDecimal就能避免偏差吗?
尝试修改代码:
System.out.println(new BigDecimal(1).subtract(new BigDecimal(0.8)));
控制台输出:
0.1999999999999999555910790149937383830547332763671875
是否又出你所料,BigDecimal确实在精度上有所提升,不过并不是想要的正确结果,所以再次修改代码:
System.out.println(new BigDecimal("1").subtract(new BigDecimal("0.8")))
控制台输出:
0.2
脱坑指南一
想要避免浮点数运算的精度偏差,需使用java.math包下提供的精度运算类BigDecimal,且必须使用字符串类型参数初始化BigDecimal。
2
浮点数的判断:用equals判断真的奏效了吗?
对于浮点数的判断通常有两种情况:1、值相等,2、值相等且精度相等。下面用equals分别测试一下:
System.out.println(new BigDecimal("1.0").equals(new BigDecimal("1")))
System.out.println(new BigDecimal("1.0").equals(new BigDecimal("1.0")))
控制台输出:
false
true
结果很直观,对于浮点数来说,equals的判定条件是值相等且精度也要相等,否则无法奏效。
那么如果只想判断值是否相等应该怎么做呢?使用compareTo方法:
System.out.println(new BigDecimal("1.0").compareTo(new BigDecimal("1"))==0);
控制台输出:
true
脱坑指南二
想要仅对浮点数的值判等用compareTo方法,值+精度判等用equals方法。
3
数值溢出:运算时数值超过类型最大值不会报错吗?
通过代码测试一下,第二行数值溢出了,首先没有编译错误:
Integer integer = Integer.MAX_VALUE;
System.out.println(integer + 1);
System.out.println(integer + 1 == Integer.MIN_VALUE);
控制台输出,也没有任何异常:
-2147483648
true
所以内存溢出不会报错,数值将回到对应数据类型的最小值重新轮回。既然问题已经产生,应该如何解决呢?
脱坑指南三
Plan A:使用Math类提供的带有溢出异常捕获的运算方法xxExact
方法源码,对溢出主动捕获异常
Plan B:使用范围更大的数据类型BigInteger,可能有读者会问“如果这个类型也不够用溢出了呢”,只能说这样问下去的话问题永远也没有答案了。
4
总结:三个坑点
1、浮点数的精度偏差
方案: 使用BigDecimal做浮点数运算,且构造入参要使用字符串类型
2、浮点数的判等问题
方案:仅对浮点数的值判等用compareTo方法,值+精度判等用equals方法
3、隐性的数值溢出问题
方案1:使用Math类提供的带有溢出异常捕获的运算方法xxExact做数值运算
方案2:使用范围更大的数据类型BigInteger