加入收藏 | 设为首页 | 会员中心 | 我要投稿 我爱制作网_潮州站长网 (http://www.0768zz.com/)- 物联安全、建站、操作系统、云计算、数据迁移!
当前位置: 首页 > 教程 > 正文

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

发布时间:2021-12-09 20:26:40 所属栏目:教程 来源:互联网
导读:浮点数会有精度损失这个在上大学的时候就已经被告知,但是至今完全没有想明白其中的原由,老师讲的时候也是一笔带过的,自己也没有好好琢磨。终于在工作的时候碰到了,于是google了一番。 问题: 对两个double类型的值进行运算,有时会出现结果值异常的问题

 浮点数会有精度损失这个在上大学的时候就已经被告知,但是至今完全没有想明白其中的原由,老师讲的时候也是一笔带过的,自己也没有好好琢磨。终于在工作的时候碰到了,于是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)+英文版+源代码 下载见 http://www.linuxidc.com/Linux/2016-11/137370.htm
 
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. 相除
 
 1 /**
 2  * 相除
 3  * @param double1
 4  * @param double2
 5  * @param scale 除不尽时指定精度
 6  * @return
 7  */
 8 public static double div(String doubleValA, String doubleValB, int scale) {  
 9     BigDecimal a2 = new BigDecimal(doubleValA);  
10     BigDecimal b2 = new BigDecimal(doubleValB);
11     return a2.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();  
12 }
5. 主函数调用
 
1 public static void main(String[] args) {
2     String doubleValA = "3.14159267";
3     String doubleValB = "2.358";
4     System.out.println("add:" + add(doubleValA, doubleValB));
5     System.out.println("sub:" + sub(doubleValA, doubleValB));
6     System.out.println("mul:" + mul(doubleValA, doubleValB));
7     System.out.println("div:" + div(doubleValA, doubleValB, 8));
8 }
结果展示如下所示:
 
  add:5.49959267
  sub:0.78359267
  mul:7.40787551586
  div:1.33231241
 
所以最好的方法是完全抛弃double,用string和java.math.BigDecimal。
 
  java遵照IEEE制定的浮点数表示法来进行float,double运算。这种结构是一种科学计数法,用符号、指数和尾数来表示,底数定为2——即把一个浮点数表示为尾数乘以2的指数次方再添上符号。具体底层如何存储以及如何进行运行请继续关注我的博客,后续我会将详情总结好的。

(编辑:我爱制作网_潮州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读