zoukankan      html  css  js  c++  java
  • 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,否则肯定会有精度损失。

    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的指数次方再添上符号。具体底层如何存储以及如何进行运行请继续关注我的博客,后续我会将详情总结好的。

  • 相关阅读:
    sqlhelper使用指南
    大三学长带我学习JAVA。作业1. 第1讲.Java.SE入门、JDK的下载与安装、第一个Java程序、Java程序的编译与执行 大三学长带我学习JAVA。作业1.
    pku1201 Intervals
    hdu 1364 king
    pku 3268 Silver Cow Party
    pku 3169 Layout
    hdu 2680 Choose the best route
    hdu 2983
    pku 1716 Integer Intervals
    pku 2387 Til the Cows Come Home
  • 原文地址:https://www.cnblogs.com/liang1101/p/6392179.html
Copyright © 2011-2022 走看看