java浮点数精度损失原理和解决

来源:互联网 发布:网络咨询医生有前途吗 编辑:程序博客网 时间:2024/06/06 07:30

原理

浮点数会有精度损失,如下代码所示

public class NumTest {    public static void main(String[] args) {        double a =1;        double b =0.99;        System.out.println(a-b);    }}

这段代码运行结果很简单,不是0.01么?!

  在这一天之前如果问我结果是什么我也会毫不犹豫的答道0.01,然而真实的结果是:0.010 00000 00000 00009。

  虽然运算结果不太对,但是这个结果和0.01相差不大会产生影响么?那看下面这一段代码:

public class NumTest {    public static void main(String[] args) {        double a =1;        double b =0.99;        System.out.println(a-b);        if((a-b) == 0.01){            System.out.println("1 - 0.99 == 0.01");        }else{            System.out.println("1 - 0.99 != 0.01");        }    }}

输出的结果也在意料之中:1 - 0.99 != 0.01

  如果在程序中直接使用double会造成精度损失,极有可能对造成一些莫名奇妙的bug。

  但是所有的浮点数都会有精度损失么?
  

package sort;public class NumTest {    public static void main(String[] args) {        double a = 0.5;        double b = 0.25;        System.out.println(a-b);        if((a-b) == 0.25){            System.out.println("no problem");        }else{            System.out.println("has problem");        }    }}

输出的结果是:no problem,也就是说double类型的0.5和0.25在运算的时候没有出现精度损失。

  关于精度损失的原理可以很简单的讲,首先一个正整数在计算机中表示使用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一样。
  

解决方案

一般遇到这种需要用到浮点数运算的地方都可以使用java.math.BigDecimal。

  首先需要注意的是,直接使用字符串来构造BigDecimal是绝对没有精度损失的,如果用double或者把double转化成string来构造BigDecimal依然会有精度损失,所以我觉得这种解决方法就是在数据库中就把浮点数用string来表示存放,涉及到运算直接用string构造double,否则肯定会有精度损失。
  

package sort;import java.math.BigDecimal;public class NumTest {    public static void main(String[] args) {        String a = "301353.0499999999883584678173065185546875";        double c = 301353.0499999999883584678173065185546875d;        BigDecimal sa = new BigDecimal(a);        BigDecimal sc = new BigDecimal(String.valueOf(c));        BigDecimal dc = new BigDecimal(Double.toString(c));        System.out.println("sa : "+ sa);        System.out.println("sc : "+ sc);        System.out.println("dc : "+ dc);    }}

上述代码的输出结果是:

  sa : 301353.0499999999883584678173065185546875

  sc : 301353.05

  dc : 301353.05

所以最好的方法是完全抛弃double,用string和java.math.BigDecimal。

原创粉丝点击