本节摘要:舍入误差在运算中按确定规则传播:加减法看绝对量级,乘除法看相对量级。相近数相减引发灾难性抵消,求和顺序改变累积偏差,而病态问题会把输入端的小扰动放大到淹没解。本节用三个可复现实验建立误差的定量直觉。
阅读完本节,你应当能够:
浮点运算里每一次加减乘除都会附加一次新的舍入,但传播方式不同:乘除法的相对误差近似相加,x(1+\delta_1) \cdot y(1+\delta_2) 的相对误差约为 \delta_1 + \delta_2,仍然是 eps 量级,无伤大雅;加减法看的是绝对量级——两个大数相加,结果很大,相对误差依然温和;相近的大数相减才是重罪:结果很小,而绝对误差继承自大数的刻度,相对误差瞬间爆炸。
一个数值实验作为量尺:
import numpy as np x = 1.2345678901234567 y = 1.2345678901234499 true_diff = 1.2345678901234567 - 1.2345678901234499 # 数学值约 6.8e-15 rel_err = abs((x - y) - 6.8e-15) / 6.8e-15 print(f"差的绝对误差来自 1 附近的刻度约 2.2e-16,而差本身只有 6.8e-15") print(f"因此相对误差可达 {2.2e-16 / 6.8e-15:.1%} 量级")
16 位有效数字的操作数,相减后只剩约 2 位可靠数字——这就是"灾难性抵消"的量化含义。
求解 x^2 - 10^8 x + 1 = 0。两根约为 1e8 和 1e-8。用教科书公式直接算小根:
import numpy as np b, c = 1e8, 1.0 r1 = (-b + np.sqrt(b*b - 4*c)) / 2 r2 = (-b - np.sqrt(b*b - 4*c)) / 2 print(r1, r2) # r1 = 1e8 没问题;r2 = -7.450580596923828e-9 —— 连正负号都错了!
取证:判别式 b^2 - 4 约为 1e16,此时浮点刻度已经是 2,减去 4 等于没减;随后 -b + \sqrt{\cdot} 是两个 1e8 量级相近数的减法,抵消把有效数字扫光。破案:利用韦达定理,两根之积等于 c。先算没有抵消的大根 r1,再用 r2 = c / r1 反求小根:
r2_fixed = c / r1 print(r2_fixed) # 1e-8,完全精确
同一公式、同一机器,只改了一条代数恒等式,结果从错误 15 个数量级变成全精度。这就是数值分析反复强调的:公式的数学等价不等于数值等价。
把 0.1 加一百万次,与直接算 1e6 乘 0.1 对比:
import numpy as np n = 10**6 naive = 0.0 for _ in range(n): naive += 0.1 print(naive - n * 0.1) # 约 -7.6e-10,偏了几十亿倍 eps print(np.sum(np.full(n, 0.1)) - n * 0.1) # 约 1.1e-10,成对求和小一个数量级
朴素顺序求和里,和数很快长到 1e5 量级,后面每个 0.1 都在"刻度缝隙"边缘被舍入,误差持续单方向累积。NumPy 的 sum 用分治成对求和,把误差增长从 O(n) 压到 O(log n)。注意实验要设 np.full(n, 0.1) 让数组元素本身相同,比较的才是纯求和顺序效应。
解读:这个实验解释了为什么并行归约、分块累加不只是性能技巧,更是精度技巧——它改变了舍入误差的传播路径。1.3 节的 Kahan 算法给出更极致的方案。
前两个案件都是算法层的,但有一类案件,责任在问题本身。经典病态函数(Wilkinson 多项式的简化版):
import numpy as np def f(x): return (x - 1) * (x - 2) * (x - 3) # 数学上 x=2 是根。给 x 加一个 eps 量级的扰动看函数值变化: for dx in [0.0, 1e-16, 2e-16, 5e-16]: print(dx, f(2.0 + dx)) # 函数值在 0 附近抖动 —— 但这还算温和
更极端的例子是取整敏感函数:输入端最后一位的抖动让输出相对变化百分之几十。条件数就是这种敏感度的度量:问题的条件数大,意味着输出相对误差最多可达输入相对误差的多少倍。条件数是问题的属性,与算法无关——病态问题上,换成任何算法都救不回来,只能重构问题(换变量、换尺度、正则化)。
💡 关键直觉:条件数是被告的"体质",算法稳定性是"医生的手术水平"。手术再好也治不了晚期病人体质——先看病态不病态,再选算法。第二章会把这套判断做成线性系统的标准流程。

还有一个反向教训:即便问题良态,把中间结果"随手存成低精度"也会人为制造病态。某团队把仿真中间量写入 float32 文件再读回继续算,结果精度永远卡在 1e-7 量级,排查两周才发现瓶颈不在算法而在存储格式。误差链的强度由最弱的一环决定——这个原则在后面每一章都会以不同面目重现。
下一节进入结案阶段:当改写公式和换顺序都不够用时,Kahan 补偿与任意精度算术这两件重武器如何登场。