我们在开发过程中,有可能会遇到double类型中小数的精度控制的问题,比如数值的计算,小数位的控制等等,但是经常会遇到精度控制不当,导致计算结果出错的问题,以下为你介绍double的底层实现以及为什么会出现精度问题。

问题示例

看以下的代码

    @Test
    public void test(){
        System.out.println(1.54-1.55);
    }

输出结果,并不是我们预期的结果

image

进制转换

不过在此之前,可能还需要了解以下进制转换的知识,可以看看这篇文章Java中的double类型是怎么存储的

问题探讨

由这篇文章Java中的double类型是怎么存储的可知,1.54的二进制计算方式如下:

整数部分:

计算 余数 顺序
1÷2 0 此时商为0,不再计算 1 1

小数部分:

计算 结果a a>=1?1:0 顺序
0.54×2 1.08 1 1
0.08×2 0.16 0 2
0.16×2 0.32 0 3
0.32×2 0.64 0 4
0.64×2 1.28 1 5
0.28×2 0.56 0 6
0.56×2 1.12 1 7
0.12×2 0.24 0 8
0.24×2 0.48 0 9
0.48×2 0.96 0 10
0.96×2 1.92 1 11
0.92×2 1.84 1 12
0.84×2 1.68 1 13
0.68×2 1.36 1 14
0.36×2 0.72 0 15
0.72×2 1.44 1 16
0.44×2 0.88 0 17
0.88×2 1.76 1 18
0.76×2 1.52 1 19
0.52×2 1.04 1 20
0.04×2 0.08 0 21
0.08×2 0.16 0 22
0.16×2 0.32 0 3

此时,会发现,小数部分计算出的第2位和第22位,出现了相同的,意味着又要开始进行相同的计算,意味着这是一个无限循环的数字。

由此可见,1.54 的二进制为:

1.1000101000111101011100001010001111010111000010100011110101110············

计算机不可能为我们存下这种无止境的数字,这种不正常的数字,double底层会自动按照double的限制,截取有效数位即可,所以,才会出现有些数字精度不准确的情况

继续,这篇文章Java中的double类型是怎么存储的中将其按照科学计数法的方式来计算double存储的结构,同理,对于1.54这种不正常的数字,我们也可以来试着计算:

double存储结构

通过科学计数法可知:

十进制:1.54

二进制:1.1000101000111101011100001010001111010111000010100011

科学计数法:1.1000101000111101011100001010001111010111000010100011 * 10010^0

小数点移动位数:0位

由上可知:double会自动将上面的无限循环的二进制截取为64位,所以,符号,指数,尾数,各保留对应的位数即可。

符号位:0 ----->因为是正数

指数位

  • 偏移量为:2e11=21111=10232^{e-1}-1 = 2^{11-1}-1 = 1023
  • 指数位:科学计数法上的指数+偏移量=0+1023=1023科学计数法上的指数+偏移量 = 0+1023 = 1023
  • 指数位二进制:1111111111

指数位的二进制为:1111111111 -----> 10个1

尾数位:1000101000111101011100001010001111010111000010100011

综上所述:1.54的double的在内存的存储方式为:

0011111111111000101000111101011100001010001111010111000010100011

消除第一个0–符号位,因为是正数,消除第二个0–指数位前面的0可以直接不写
如果是负数,第一位必然是1,那么指数位就需要将前面的不管多少位0都要写出。

11111111111000101000111101011100001010001111010111000010100011

验证:

    @Test
    public void test() {
        double test = 1.54d;
        long testBits = Double.doubleToLongBits(test);
        System.out.println(Long.toBinaryString(testBits));
    }

image-1648960076995
总结:最后的 00010100011 是不准确的,因为double只能取到这儿了,尾数只能存52位,所以说,有时候计算的时候,就会导致精度出错的问题,从而导致最上面的结果。