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

根子只有一条:位数有限。无限循环小数只能截断,而误差会在每一次运算里重新发生并累积。

📝 本节和前面几节的关系

3.2 说明了 0.1 写不完,这一节把【算出错】的链条完整接上。

一、根子在【位数有限】

0.1 的二进制是无限的,而存储单元是有限的——只能砍。 单精度留 23 位尾数、双精度留 52 位,多出来的部分一律舍弃。 所以严格地说:计算机里存的 0.1,从来就不是 0.1。 这是第一层误差,叫表示误差——东西还没开始算,就已经不准了。

二、第二层:每一次运算还要再舍一次

两个近似值相加,结果通常又是一个位数超标的数。 于是每一次运算结束都要再舍一次——这就是第二层误差。 两层叠起来,就有了第三层:误差累积。 累加 100 次,就是舍了 100 次;这也解释了为什么【累加】比【乘 100】误差更大。

三、用 0.1 的三次累加看清链条

第一步:0.1 存进去已经带偏差。 第二步:0.1 + 0.1 得 0.2,恰好还在格子上,看不出问题。 第三步:0.2 + 0.1 得到 0.30000000000000004——偏差第一次露出来。 所以不是【加得多了才错】,而是错一直在,只是迟早会跑到小数点后第 17 位去。

四、这不是缺陷,是取舍

用有限位表示无限的量,必然有误差,这是数学上的硬约束,不是工程没做好。 换来的是什么?速度快、范围大、一套硬件能同时管【极小】和【极大】。 所以正确的心态不是【想办法消掉误差】,而是:搞清楚误差有多大、什么时候要紧、什么时候不要紧。 后面两节讲的浮点结构,就是用来判断【误差能有多大】的。

五、动手 Demo:出错的原因就是【截断】

计算机的位数是有限的,无限循环的小数只能被截断或舍入。

s = 0.0
for i in range(1, 4):
    s += 0.1
    print("第 %d 次累加 = %r" % (i, s))
print("乘 100 次 = %r" % (0.1 * 100))

实跑输出:

--------------------------------------------------------------------
出错的原因就是【截断】
--------------------------------------------------------------------

计算机的位数是有限的,**无限循环的小数只能被截断或舍入**。

  0.1 的真实二进制:0.0001100110011001100110011…(0011 无限重复)
  单精度只能留 23 位尾数,双精度只能留 52 位——后面的全丢。

  精度       尾数位     存下的 0.1 的真值                  与真值差
  --------------------------------------------------------------------
  单精度     23         0.1000000014901161193847656250     约 1.5e-09
  双精度     52         0.1000000000000000055511151231     约 5.6e-18

  两条推论:
    **位数越多越精确,但永远不会精确**——0.1 就是写不完。
    **误差会累积**——每一步舍一次,加一百次就舍一百次。

  这也解释了为什么 0.1 累加三次就偏:**每一跳都落不到格子上。**

单精度只留 23 位尾数、双精度只留 52 位,存下的 0.1 与真值都差一点点。 误差会累积:每一步舍一次,加一百次就舍一百次——所以累加比乘法更容易露馅。

30 秒实验

  1. 把 0.1 累加 100 次和直接乘 100,两个结果一样吗?动手比一比。
  2. 想想 1/3 在十进制里也写不完——每一套计数法都有它【写不完的数】。

常见误区

  1. 以为这是【计算机算不准】。它算得很准,只是输入的数一开始就不精确。
  2. 以为换成更大精度就没事了。精度再高也只是把出错的位置往后推。
自测三问(点开对答案) 1. 计算机计算小数出错的三层原因是什么? —— 表示误差(存进去就不准)、运算误差(每步舍一次)、误差累积(舍得越多偏得越多)。 2. 为什么累加比乘法更容易看出误差? —— 累加要一步一舍,乘法只舍一次,累积的舍入次数不同。 3. 这是工程的缺陷吗? —— 不是。用有限位表示无限量必然有误差,换来的是速度、范围和统一硬件。

相关笔记