如何理解double精度丢失问题?

比如 1.1+0.1=1.2000000000000002 在java C++ OC等语言中出现是否原理相同。
关注者
49
被浏览
99,806

6 个回答

很简单,从二进制角度考虑问题即可:把「0.1」转成二进制的表示,然后还原成十进制,就能看出问题。

一、把 0.1 转成二进制表示

我们知道 DEC(1) 就是 BIN(1),但是 DEC(0.1) 怎么转换成二进制?

对了!用除法:

0.1 = 1 ÷ 10

很简单,二进制就是要算

1 ÷ 1010

我们回到小学的课堂,来列竖式吧:

       0.000110011...
      ------------------
1010 ) 1 0000
         1010
       ------
          1100
          1010
          ----
            10000
             1010
            -----
              1100
              1010
              ----
                10

相信上过小学的你一定会发现,除不尽,除出了一个无限循环小数:二进制的 0.0001100110011...

二、舍入

我们得把 0.0001100110011... 放进一个 double「双精度浮点数」里面

双精度浮点数能表示多少精度呢?查看文档会发现:

  • 半精度(16bit):11 位有效数字
  • 单精度(32bit):24 位有效数字
  • 双精度(64bit):53 位有效数字
  • 四精度(128bit):113 位有效数字

好吧,双精度是 53 位有效数字

0.00011001100110011001100110011001100110011001100110011001 10011...

方便起见,我在第 53 个有效数字后面加了个空格。

那么问题来了:十进制数我们可以四舍五入,二进制怎么办?精神是一样的:待处理部分有没有达到前一位的一半,达到就进位,没达到就舍去。(暂且当作 0 舍 1 入。)

那么我们的 0.1 在 double 中就是

0.00011001100110011001100110011001100110011001100110011001 10011...
0.00011001100110011001100110011001100110011001100110011010

而 1.1 就是

1.0001100110011001100110011001100110011001100110011001 10011...
1.0001100110011001100110011001100110011001100110011010

三、加法

这个很简单,1.1 + 0.1 就是

  1.0001100110011001100110011001100110011001100110011010
+ 0.00011001100110011001100110011001100110011001100110011010
------------------------------------------------------------
  1.00110011001100110011001100110011001100110011001100111010

因为结果仍然是 double,需要再做一次保留 53 位有效数字和舍入:

1.0011001100110011001100110011001100110011001100110011 1010
1.0011001100110011001100110011001100110011001100110100

四、结果

好了,终于可以回到十进制的世界了,我们把最终结果转换回来:

1.0011001100110011001100110011001100110011001100110100

得到十进制的:

1.20000000000000018

一般的输出函数,在输出浮点数时,都会限制显示的有效数字,即会再做一次四舍五入。题目中的 1.2000000000000002 是这个结果在显示时四舍五入后的结果。

1.20000000000000018
1.2000000000000002

---

正经答题:1.2000000000000002 的原理上面已经一步步分析了。至于各个语言之间的差异,答案是可能会有,比如可能因为选择的舍入规则的不同可能导致的结果的不同;甚至有可能某个语言里的浮点数压根不是 IEEE 754 的浮点数,而是以字符串方式保存的,所以可能没有误差。

原理是一样的,都遵循同样的标准。

根本原因在于,数学意义上的小数不是每个都能用二进制在有限位数内精确的表示。

像 0.1,1.1 这样的小数没有精确的二进制表示,然后求和就不是1.2了。