seegongsik
工程数学

浮点数 条件数

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

到现在为止,我们把误差看成从方法来的。步子太大,次数太低。可是所有这些方法下面,铺着一个更根本的限度。计算机不能精确地存下一个实数。连 0.1 也是这样。64 位浮点数只记得大约 15 到 16 位,其余都舍入丢掉。通常这个微小的误差埋着看不见,可是几乎相同的两个数相减、把小误差堆上几百万次、或者遇上答案对输入敏感的坏条件数,它就忽然跳出来。这一讲看数值分析的地板,也就是计算机处理数的方式本身,和从那里漏出的误差。误差的地板 (roundoff floor) 从哪里来,这就是它的身份。

数轴上的金色刻度,是计算机能够精确存下的数。第一眼就奇怪。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 的误差就稳步变大。一次看不见的,几百次就清楚了,像仿真那样重复几十亿次的计算,可能毁掉结果。所以数值程序员把加法的顺序改成从小的开始,或者用卡汉求和这样的补偿求和来管住这个累积。

最可怕的是另一件。减去两个几乎相同的数。在小的 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⁻¹⁶)。这个微小的舍入误差通常埋着,可是在三种情形里跳出来。仿真里小误差的累积、几乎相同的数相减造成的灾难性抵消、以及答案对输入敏感的坏条件数。前两种用计算顺序或等价的式子把算法改写成稳定的来减小,条件数是问题本身的性质,更好的计算机也修不好。数值分析的两根柱子从这里出来。选择稳定的方法,并把问题改成条件良好的。这是一切数值计算的地板。
← 工程数学
如果有帮助,请支持我们