seegongsik
我的单词本
工程数学

计算机只能近似地知道数

计算机浮点的极限,机器精度与舍入累积,灾难性相消与条件数

至今我们都把误差看作来自方法:步子太大、阶数太低。但在每一种方法之下,还铺着一个更根本的极限。计算机无法精确存储实数,连 0.1 都不行。64 位浮点只记住大约 15 到 16 位,其余四舍五入丢掉。通常这点微小误差被掩埋、看不见,可一旦你相减两个几乎相等的数、把小误差累积上百万次、或遇到答案对输入敏感的问题(坏条件数),它就会突然冒出来。这一讲看的是数值分析的地基,也就是计算机处理数的方式本身,以及从中泄漏的误差。这正是你在上一讲见到的舍入误差地板的真身。

数轴上的刻度,就是计算机能精确存储的数。乍看很奇怪:靠近 0 密集,越往右越稀疏。这是因为浮点用几位有效数字加一个指数来写数,很像科学记数法。所以每当大小翻倍(一个八度),相邻可表示值之间的间隔也恰好翻倍。这个最小间隔叫 ulp(units in the last place)。移动 x,就能看到数越大 ulp 越大。这意味着大数存得粗、小数存得细。一切舍入误差都从这里开始。

我们就在 1 附近量一量那间隔到底多小。把 ε = 2k 加到 1 上,并逐步降低 k。到某一刻,1+ε 就直接变成 1。这是因为一旦加上的 ε 小于 1 上方可表示间隔的一半,它就被舍入到最近的可表示值,也就是 1 本身。这个边界就是机器精度:在 float64 中是 2⁻⁵² ≈ 2.2×10⁻¹⁶,约合 15 到 16 位有效数字。这不是空谈,而是真实的运算。对计算机来说,1 加上一个极小的数可以就是 1。这是精度有地板的第一个证据。

单个误差在 10⁻¹⁶ 量级,看似可以忽略。可 0.1 在二进制里除不尽,存进去的那一刻就已带着一个极小的误差。每加一次,误差也跟着积一点。用滑块增加相加次数,看与真值 n/10 的偏差稳步变大。单步看不见的,几百次后就清楚了;在像仿真那样重复上十亿次的计算里,足以毁掉结果。所以数值程序员会改变相加顺序(从小到大),或用补偿求和(Kahan 求和)来驯服这种累积。

最可怕的是另一种:相减两个几乎相等的数。在小 x 处计算 (1−cos x)/x²。真值在 x 变小时应趋于 0.5。可金色曲线(朴素计算)到某处就塌成 0。x 小时 cos x 几乎为 1,于是 1−cos x 几乎丢光有效数字:高位相同被抵消,只剩不可信的低位。这就是灾难性相消。蓝色曲线把同一个值改写成 1−cos x = 2 sin²(x/2) 再算。因为它根本不做减法,就一路守住 0.5。公式一样,但运算顺序决定了精度的生死。

最后一个不是关于误差,而是关于问题本身的性质。解方程组就是找两条线的交点。两条线干脆地相交,交点就清晰。可如果它们几乎平行呢?把一条线动一根头发丝,交点就窜到老远。用滑块降低斜率,让两线接近平行:同样 10% 的扰动,解却越动越大。这个放大倍数就是条件数。条件数大(病态),输入的小误差就在答案里被放大。可怕的是,这与舍入误差不同,再好的计算机也修不了,因为问题本身就敏感。所以好的数值分析,既要稳定的算法,也要良态的问题,两手都要抓。

实际应用计算机只用大约 15 到 16 位有效数字存储实数。可表示的数在 0 附近密集、越大越稀疏,间隔(ulp)每个八度翻一倍。与 1 可区分的最小数就是机器精度(float64 ≈ 2.2×10⁻¹⁶)。这点微小的舍入通常被掩埋,却在三种情形里冒出来:小误差的累积(仿真)、相减几乎相等的数导致的灾难性相消,以及答案对输入敏感的坏条件数。前两者靠把算法改写得稳定来减小(运算顺序、等价公式);条件数是问题本身的性质,再好的计算机也修不了。数值分析的两根支柱由此而来:选一个稳定的方法,并把问题改造成良态。这是一切数值计算的地基。
工程数学
如果有帮助,请支持我们