⚙️ 第三章 计算机在计算小数时会出错的原因
📄 一句话概括

把 0.1 累加 100 次,得到的是 9.99999999999998——不是计算错了,是存进去的那 0.1 本来就不是 0.1。

📝 本节和前面几节的关系

第二章讲完了整数怎么用二进制表示,这一章接着问:那小数呢?这一节先用一个实验把问题摆出来。

一、先把实验做一遍

把 0.1 累加 100 次,结果是 9.99999999999998——不是 10,也没错。 更意外的是:不用等到一百次,加到第三次就偏了。 0.1 + 0.1 + 0.1 得到的是 0.30000000000000004。 教材用这个实验开篇,因为它足够小、足够怪、又足够普遍。

二、别急着怪语言

这不是 Python 的毛病,也不是某台机器坏了。 C、Java、JavaScript、Excel——所有用二进制浮点的东西,行为都一样。 所以它既不是 bug,也不是精度调参能解决的事。 它是【这种表示法本身的性质】。

三、这个现象说明什么

两条线索: 一、十进制的 0.1 在二进制里写不完——它是个无限循环小数。 二、计算机只能存有限位——写不完的部分只能砍掉。 于是存进去的那个数,已经不是 0.1 了,而是 0.1 的近似值。 拿近似值做一百次加法,结果自然不会正好是 10——误差是算出来的,不是坏出来的。

四、接下来要回答的两个问题

第一个问题:二进制到底怎么表示小数?为什么 0.1 偏偏不行?——3.2 回答。 第二个问题:既然会错,为什么计算机还敢用它算卫星轨道?——3.3 到 3.5 回答。 这一章的最后两节,会给出【确认误差】和【绕开误差】的具体做法。

五、动手 Demo:0.1 累加 100 次,结果不是 10

这是本书最有名的一个实验。不光一百次,有时加到第三次就偏了。

s = 0.0
for _ in range(100):
    s += 0.1
print(s, s == 10.0)
print(0.1 + 0.1 + 0.1)

实跑输出:

--------------------------------------------------------------------
0.1 累加 100 次,结果不是 10
--------------------------------------------------------------------

这是本书最有名的一个实验。**不光一百次,有时加到第三次就偏了。**

  把 0.1 累加 100 次(双精度浮点):
    结果      9.99999999999998
    等于 10?  False
    差多少    -1.954e-14

  再看误差是第几步冒头的:

  累加几次     结果                    等于 n/10 吗
  --------------------------------------------------
  1            0.1                     是
  2            0.2                     是
  3            0.30000000000000004     否
  10           0.9999999999999999      否
  100          9.99999999999998        否

  头两次还看不出来,**加到第三次就露馅了**。
  这不是 Python 的毛病——任何用二进制浮点的语言都一样。

实测结果:累加 100 次得 9.99999999999998,与 10 相差约 -1.95e-14。 累加三次就已经偏了(0.30000000000000004)——误差不是攒到一百次才出现的。

30 秒实验

  1. 在你的语言里跑一遍这个实验,看看结果和这里是否一样。
  2. 再试试 0.2 + 0.4 和 0.1 + 0.7,看哪几对能正好凑上。

常见误区

  1. 以为这是语言或库的 bug。所有二进制浮点都一样,这是表示法的性质。
  2. 以为【多留几位小数】就能修好。问题不在小数位数,在二进制写不完。
自测三问(点开对答案) 1. 0.1 累加 100 次得到什么? —— 9.99999999999998,不是 10,与 10 相差约 -1.95e-14。 2. 这个现象是 bug 吗? —— 不是。所有用二进制浮点的语言、软件都一样,是表示法本身的性质。 3. 误差为什么会累积? —— 存进去的 0.1 已是近似值,每一跳都带一点偏差,加一百次就偏一百次。

相关笔记