Java浮点数计算精度损失底层原理与解决方案

  浮点数会有精度损失这个在上大学的时候就已经被告知,但是至今完全没有想明白其中的原由,老师讲的时候也是一笔带过的,自己也没有好好琢磨。终于在工作的时候碰到了,于是google了一番。

问题:

  对两个double类型的值进行运算,有时会出现结果值异常的问题。比如: 

1 System.out.println(19.99+20); 2 System.out.println(1.0-0.66); 3 System.out.println(0.033*100); 4 System.out.println(12.3/100);

输出:

39.989999999999995
0.33999999999999997
3.3000000000000003
0.12300000000000001

  Java中的简单浮点数类型float和double不能够精确运算。这个问题其实不是JAVA的bug,因为计算机本身是二进制的,而浮点数实际上只是个近似值,所以从二进制转化为十进制浮点数时,精度容易丢失,导致精度下降。

关于精度损失的原理可以很简单的讲,首先一个正整数在计算机中表示使用01010形式表示的,浮点数也不例外。

  比如11,11除以2等于5余1

       5除以2等于2余1

       2除以2等于1余0

       1除以2等于0余1

  所以11二进制表示为:1011.

  double类型占8个字节,64位,第1位为符号位,后面11位是指数部分,剩余部分是有效数字

  正整数除以2肯定会有个尽头的,之后二进制还原成十进制只需要乘以2即可。

  举个例子:0.99用的有效数字部分,

       0.99 * 2 = 1+0.98 --> 1

       0.98 * 2 = 1+0.96 --> 1

       0.96 * 2 = 1+0.92 -- >1

       0.92 * 2 = 1+0.84 -- >1

         ...............

  这样周而复始是没法有尽头的,而double有效数字有限,所以必定会有损失,所以二进制无法准确表示0.99,就像十进制无法准确表示1/3一样。

解决办法:

  在《Effective Java》中提到一个原则,那就是float和double只能用来作科学计算或者是工程计算,但在商业计算中我们要用java.math.BigDecimal,通过使用BigDecimal类可以解决上述问题,首先需要注意的是,直接使用字符串来构造BigDecimal是绝对没有精度损失的,如果用double或者把double转化成string来构造BigDecimal依然会有精度损失,所以我觉得这种解决方法就是在使用中就把浮点数用string来表示存放,涉及到运算直接用string构造double,否则肯定会有精度损失。

《Effective Java中文版 第2版》.(Joshua Bloch)(高清pdf)+英文版+源代码 下载见

1. 相加

1 /** 2 * 相加 3 * @param double1 4 * @param double2 5 * @return 6 */ 7 public static double add(String doubleValA, String doubleValB) { 8 BigDecimal a2 = new BigDecimal(doubleValA); 9 BigDecimal b2 = new BigDecimal(doubleValB); 10 return a2.add(b2).doubleValue(); 11 }

2. 相减

1 /** 2 * 相减 3 * @param double1 4 * @param double2 5 * @return 6 */ 7 public static double sub(String doubleValA, String doubleValB) { 8 BigDecimal a2 = new BigDecimal(doubleValA); 9 BigDecimal b2 = new BigDecimal(doubleValB); 10 return a2.subtract(b2).doubleValue(); 11 }

3. 相乘

1 /** 2 * 相乘 3 * @param double1 4 * @param double2 5 * @return 6 */ 7 public static double mul(String doubleValA, String doubleValB) { 8 BigDecimal a2 = new BigDecimal(doubleValA); 9 BigDecimal b2 = new BigDecimal(doubleValB); 10 return a2.multiply(b2).doubleValue(); 11 }

4. 相除

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/d900cea04f0d58aa84de4d292fceff42.html