在实数里,1+x−1 与 x/(1+x+1) 是同一个量。可把 x 取成 10−16,用常见的双精度运算直接算前一个式子,你可能得到 0;后一个式子却能留下约 5×10−17。这不是小数位显示得不够多,差别已经发生在计算过程中。
我们从计算机怎样存数讲起。把这件事弄清楚以后,公式中的“近似”就不再是一个含糊的符号:你能指出哪里舍入了,误差相对哪个量很小,以及哪一步会把它放大。
一把间距会改变的数尺
十进制科学记数法把数写成有效数字乘以 10 的幂。二进制浮点数做的是相似的事。暂时只看规格化非零数,写成
(−1)s(1.b1b2⋯b
这里 s 决定符号,e 决定尺度,p 表示包含开头那个 1 在内的有效二进制位数。小数点后只有 p−1 位。这个计数容易混:常见 binary64 存储 52 个小数位,加上不必显式存储的首位 1,规格化数共有 53 位有效精度。
先用很小的 p=4 看清结构。在 [1,2) 内,可表示的数只有
1, 1.125, 1.25, 1.375, 1.5, 1.625, 1.75, 1.875.
它们对应 1.0002 到 1.1112,间距都是 2−3=0.125。乘以 2 后得到 内的数,间距变为 ;除以 2 后, 内的间距是 。同一段内均匀,跨过 2 的幂,间距改变。

同一组四位二进制数乘以 2 后,数值与相邻差都翻倍;方框表示对应关系,不表示数轴上的距离。
一般地,在 [2e,2e+1) 内,每增加末位的一个单位,数值增加
ulp=2e−(p−1).
ulp 在这里指该段的一个末位单位。浮点数的绝对间距会随大小改变,保留的相对精细程度却大致一致。不要把“有效位数固定”理解成“小数点后位数固定”。
例:用四位二进制精度存储 0.2。 它处于 [2−3,2−2),这一段的间距为 2−6=。围住它的两个浮点数是 与 ,距离分别为 和 。最近舍入选 。二进制表示为 ,相对误差为
0.20.203125−0.2=0.015625.
0.2 在十进制里很短,换到底数 2 却有无限循环的小数展开。存储误差来自所用进制和位数,不是因为这个数“复杂”。
舍入到最近的点,中点怎么办
本课默认使用舍入到最近值,中点取偶数。先找距离最近的可表示数;恰好位于两个数的正中间时,选有效数字末位为 0 的那个,也就是对应整数有效数为偶数的一侧。
在刚才的 p=4 数尺上,1.0625 恰好位于 1.0002 与 1.0012 的中间,所以舍入成 1。另一个中点 位于 与 之间,这次选 。中点取偶数不是总向下,也不是十进制里一律“五入”。

两个中点分别向左和向右舍入,选择依据都是有效数字末位为 0。
设非零实数 x 的大小落在规格化范围内,舍入没有导致上溢。在所属的二进制段内,最近舍入的绝对误差最多为半个间距;在段的边界附近,较小一侧的间距只会更细,不会破坏下面的上界。于是
∣fl(x)−x∣≤212e
从而
∣x∣∣fl(x)−x∣≤2−p=u.
u 叫舍入单位。等价地,可把计算机存下的数写为
fl(x)=x(1+δ),∣δ∣≤u.
这里的 δ 随输入改变,不是每次都取 u,更不是随机误差的概率模型。它只是一个保证误差不超出范围的写法。
binary64 的 p=53,所以 u=2−53≈1.11×10−16。而从 1 到右边相邻浮点数的距离是 ,常记为机器 epsilon。文献中这两个名字有时混用,本课始终把 用于最近舍入的相对误差界,把 用于 1 右侧的间距。
下面把精度降到看得见的程度。选 p=4,把输入移到 1.0625 和 1.1875,在查看结果之前判断中点会去哪一侧。再把指数从 0 改成 1,观察间距怎样变化。实验会标出实际输入、两个邻居和被选中的舍入点;判断依据是距离与末位,而不是箭头看上去更靠哪边。
小误差要带着尺度读
真实值为 x、近似值为 x 时,绝对误差为 ∣x−x∣;若 ,相对误差为 。绝对误差保留原来的单位,相对误差没有单位。
例如真实长度是 2.50 米,近似值为 2.49 米,绝对误差是 0.01 米,相对误差是 0.004,也就是 0.4%。若另一段真实长度仅为 0.02 米,同样的 0.01 米误差就占了一半。只说“误差只有百分之一米”,无法判断结果是否有用。

相同的绝对误差除以不同真实长度,分别得到 0.4% 和 50%;误差的尺度不能省略。
输出打印了很多位,也不保证这些位可信。可以用 −log10(相对误差) 粗略描述准确数字的数量级,不过这不是逐个核对十进制数字是否相同的定义;舍入边界还会影响逐位比较。真值为零时相对误差没有定义,应报告绝对误差或另选有意义的参考尺度。
数值计算里还有截断误差。比如用 1+x+x2/2 代替 ex,即使每一步都精确,仍省掉了后面的项。Taylor 余项告诉我们,在连接 0 与 x 的区间上有某个 ξ,使
ex−(1+x+2x2
取 x=0.1,这个绝对误差不超过 e0.1(0.1)3/6≈1.843×10。提高算术精度不会消掉被我们主动省掉的项;需要改变近似式。舍入误差和截断误差可以同时存在,但来源不同。
指数范围也有边界
前面的模型暂时没限制指数。实际 binary64 的规格化指数满足 −1022≤e≤1023,最大有限正数为
(2−2−52)21023≈1.798×10308.
在默认最近舍入模式下,过大的有限运算结果可能上溢成为正或负无穷。无穷有时反映真正的发散,有时只是中间结果超出了可表示范围;两者要区分。比如计算 a2+b2,直接平方可能上溢,即使最后的长度本来能表示。取 ,改写成
m(a/m)2+(b/m)2
就能避免这两次平方的上溢;a=b=0 时直接返回 0。这个改写仍需检查最终结果是否超出范围,极小分量也可能下溢,并不是无条件精确。
零附近还有一层缓冲。最小正规格化数是 2−1022,但它不是最小正浮点数。非规格化数以固定间距 2−1074 填充它与零之间的部分空隙,最小正值为
2−1074≈4.94×10−324.
这一段的绝对间距固定,越靠近零,能保住的相对精度越差。若正实数小于半个最小正浮点数,最近舍入会得到 0;恰好处于这个中点时,取偶数也选 0。于是 fl(x)=x(1+δ) 的形式虽仍可写,∣δ∣≤u 的保证却失效了。

binary64 的最小正数、最小正规格化数、1 右侧间距与舍入单位各有不同用途。
另有 NaN,表示不是一个可用的数值结果,例如浮点环境中的 0/0 或实数平方根的负输入;具体语言也可能先抛出异常。碰到这些结果,应追查触发的运算,不能继续把它们当普通误差做大小比较。
相消把早先的误差露出来
回到开头。对 x≥−1,有
1+x−1=
当 x 很小时,直接路径会先得到一个接近 1 的平方根。假如它已经有大约 10−16 的绝对舍入误差,减去 1 后,目标结果却只有大约 x/2,同一份绝对误差相对结果就可能变得很大。

两条公式在实数中相等,有限精度下却经历不同的运算;有理化路径把小量 x 保留在分子。
特别取 x=10−16。1 右侧的浮点间距约为 2.22×10−16,所以 binary64 的 1+x 舍入为 1。此后开方再减 1,得到 0。信息在加法时已经丢了;最后的 反而完全精确。相消不一定是减法这一步发生了巨大的舍入,也可能是它把此前积累的误差变得显眼。
有理化路径把 x 留在分子。即使分母里的 1+x 舍入为 1,分母也只是得到约为 2 的数,结果仍能保留约 x/2。第 02 章会把这条路径的误差界进一步算出来。
下表给出一组 binary64 运算结果。相对误差以“同一个已存储输入对应的高精度函数值”为参考,用来比较两条算法路径;输出只展示便于阅读的位数。
别只盯住最终一列。下面的实验把 1+x、平方根、相减和除法分别展开。把 x 从 10−4 降到 10−16,找出哪个中间量最早不再保留小量的信息。再比较图上的两条结果轨迹和误差,解释为什么有理化路径还能继续给出非零结果。
“两个接近的数相减一定很不准”也说得太绝对。例如 1.5−1.25=0.25,这些数都能被二进制精确表示,减法结果也精确。真正需要追问的是:两个操作数在进入减法之前,是否已经带着相对于最终小差不可忽略的误差。
每一步都准确一点,合起来会怎样
若基本运算的精确结果在允许的正规范围内,常用模型为
fl(a∘b)=(a∘b)(1+δ),∣δ∣≤u,
其中 ∘ 表示加、减、乘、除之一,除法要求分母非零。相对模型也要排除精确结果为零的相对比值以及前面讨论的上溢、下溢例外。精确零结果可以单独处理。
假设存入的数为 a=a(1+δ1)、,再做一次乘法。不能漏掉乘法自身的舍入:
fl(ab)=ab(1+δ
若每个 ∣δi∣≤u 且 ab=0,展开并取绝对值,得到相对误差上界
3u+3u2+u3.
u 很小时,主要是一阶的 3u。这个推导并没有假定三个误差同号,三角不等式给的是最坏情况下的保证。
相减的传播情况不同。在最后一次运算舍入之前,已有输入误差造成
(a−b)−(a−b)=
对 a=b,若 ∣δ1∣,∣δ2∣≤,其相对影响不超过
∣a−b∣∣a∣+∣b∣u.
当分母很小时,这个系数可能很大。再考虑最后的减法舍入,还会叠加一个约为 u 的相对项。这正是为什么只说“每一步误差都很小”不足以保证最终结果。

误差模型包含两个输入的舍入,以及乘法自身的舍入,三个因子缺一不可。
运算顺序也会改变结果。binary64 中,(1016+(−1016))+1 得到 1,而 10 得到 0:第二种顺序的小量 1 在内层加法中被舍掉了。实数加法的结合律不能原封不动地用于有限精度计算。
练习与核对
1. 在 p=4 的二进制系统中,列出 [4,8) 的全部规格化数,并给出间距。
把 [1,2) 中的八个数乘以 4,得到 4,4.5,5,5.5,6,6.5,7,7.5,间距为 0.5。8 属于下一段,因此不列在这个半开区间中。
2. 仍取 p=4,将 1.31 舍入到最近浮点数,计算绝对误差和相对误差,并与 u 比较。
邻居为 1.25 和 1.375,距离分别为 0.06 与 0.065,所以选 1.25。绝对误差 0.06,相对误差 0.06/1.31≈0.04580,小于 。半间距为 是绝对误差界,恰好数值上与这段采用的相对上界相同,不能据此把两种误差混为一谈。
3. 若近似值为 x=0.0003,真实值为 0,相对误差是多少?应怎样报告?
相对误差没有定义,不能除以真实值零。绝对误差为 0.0003,带上原量的单位。若问题已有容许尺度,例如满量程,可以另报告相对于该尺度的误差,但必须说明分母,不能把它冒称相对于真值的误差。
4. 设 a,b=0,输入和最后一次除法各有不超过 u 的相对舍入。写出计算商的乘法模型,并求一阶相对误差。
计算结果为 (a/b)(1+δ1)(1+δ3)/(1+。利用 ,得到相对误差 ,一阶绝对上界为 。要求没有溢出或下溢,且舍入后的分母仍非零。
5. 怎样改写 1−cost,使 t 接近零时避免两个接近 1 的数相减?是否因此消除了所有误差?
由半角公式,1−cost=2sin2(t/2)。这条路径直接计算小的正弦值,再平方,不必从接近 1 的余弦值中相减。但三角函数求值、乘法、输入本身仍有误差,极小结果还可能下溢;它针对的是这次相消,不能保证无条件精确。
6. 用 1+x+x2/2 近似 ex,取 x=0.2。给出 Taylor 余项的绝对上界。仅把存储精度翻倍,能否保证把这份截断误差减半?
上界为 e0.2(0.2)3/6≈1.629×10−3。这份误差在精确算术下仍存在,增加存储精度不能保证降低它。应增加 Taylor 项数,或选用更适合当前范围的近似方法。
7. 已知两个存储数是精确的 1.75 与 1.5。它们的差很小于操作数,是否就能断定发生了严重相消误差?
不能。两个输入以及差 0.25 都可精确表示,正常精度下减法也是精确的。若这些存储值其实只是带误差的测量近似,就需要另算输入误差对小差的影响;那是另一个问题。