1.2 舍入误差、抵消与病态问题


1.2 舍入误差、抵消与病态问题

本节摘要:舍入误差在运算中按确定规则传播:加减法看绝对量级,乘除法看相对量级。相近数相减引发灾难性抵消,求和顺序改变累积偏差,而病态问题会把输入端的小扰动放大到淹没解。本节用三个可复现实验建立误差的定量直觉。

学习目标

阅读完本节,你应当能够:

  1. 说明截断误差与舍入误差的区别,指出两者在数值微分与浮点运算中各自出现的位置
  2. 复现灾难性抵消实验并改写公式消除它
  3. 解释求和顺序为何影响结果,量化朴素求和与成对求和的差距
  4. 用条件数的观点判断"该怪数据还是该怪算法"

一、误差传播的两条基本法条

浮点运算里每一次加减乘除都会附加一次新的舍入,但传播方式不同:乘除法的相对误差近似相加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 附近抖动 —— 但这还算温和

更极端的例子是取整敏感函数:输入端最后一位的抖动让输出相对变化百分之几十。条件数就是这种敏感度的度量:问题的条件数大,意味着输出相对误差最多可达输入相对误差的多少倍。条件数是问题的属性,与算法无关——病态问题上,换成任何算法都救不回来,只能重构问题(换变量、换尺度、正则化)。

💡 关键直觉:条件数是被告的"体质",算法稳定性是"医生的手术水平"。手术再好也治不了晚期病人体质——先看病态不病态,再选算法。第二章会把这套判断做成线性系统的标准流程。

图 1.2-1 两类误差的责任认定流程

图 1.2-1 两类误差的责任认定流程

还有一个反向教训:即便问题良态,把中间结果"随手存成低精度"也会人为制造病态。某团队把仿真中间量写入 float32 文件再读回继续算,结果精度永远卡在 1e-7 量级,排查两周才发现瓶颈不在算法而在存储格式。误差链的强度由最弱的一环决定——这个原则在后面每一章都会以不同面目重现。

本节要点回顾

  • 舍入误差传播:乘除法温和,相近大数相减是重灾区;16 位有效数字相减可能只剩 2 位
  • 数值等价性:代数恒等变形式(如韦达定理反求小根)可以在不换机器的前提下恢复全精度
  • 求和顺序即精度:朴素累加误差 O(n),成对求和 O(log n),长数组求和优先用库的成对实现
  • 条件数是问题属性:病态问题换算法无用,需从问题结构上动手术
  • 责任认定两分法:先做扰动实验判断病态与否,再决定怪数据还是怪算法

下一节进入结案阶段:当改写公式和换顺序都不够用时,Kahan 补偿与任意精度算术这两件重武器如何登场。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U