📄 一句话概括
根子只有一条:位数有限。无限循环小数只能截断,而误差会在每一次运算里重新发生并累积。
📝 本节和前面几节的关系
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 秒实验
- 把 0.1 累加 100 次和直接乘 100,两个结果一样吗?动手比一比。
- 想想 1/3 在十进制里也写不完——每一套计数法都有它【写不完的数】。
常见误区
- 以为这是【计算机算不准】。它算得很准,只是输入的数一开始就不精确。
- 以为换成更大精度就没事了。精度再高也只是把出错的位置往后推。
自测三问(点开对答案)
1. 计算机计算小数出错的三层原因是什么? —— 表示误差(存进去就不准)、运算误差(每步舍一次)、误差累积(舍得越多偏得越多)。 2. 为什么累加比乘法更容易看出误差? —— 累加要一步一舍,乘法只舍一次,累积的舍入次数不同。 3. 这是工程的缺陷吗? —— 不是。用有限位表示无限量必然有误差,换来的是速度、范围和统一硬件。- ☐ 下一节(3.4 什么是浮点数)
相关笔记
- 如何用二进制表示小数 — 写不完是误差的源头
- 什么是浮点数 — 看清误差能有多大
- 🔵 计算机基础 · 索引 — 硬件底层的入门课
- ⚙️ 程序是怎样跑起来的 · 索引