【数值分析】0.4 有效数字缺失

Abstract: 本文介绍计算机在计算中有可能引发的有效数字缺失
Keywords: 有效数字缺失

有效数字缺失

对计算机硬件或者底层功能足够了解,虽然表面教条枯燥,但是我们能在其中学到很多有可能出现,但是不容易察觉的陷阱

有效数字

在了解有效数字缺失之前,我们有必要确定下什么是有效数字,无论是小数还是大数,我们都能写成科学计数法的形式,在十进制下我们可以把所有数字写成:
$$
d_1.d_2\cdots d_n \times 10^p
$$
的这种形式,其中 $d_1\neq 0$,有效数字是 $d_1.d_2\cdots d_n$ 共 $n$ 位,注意我们这里之研究数学上的有效数字,物理中测量还有另一套说法,我们不考虑.
有效数字我们知道是什么了,那么什么情况下会缺失呢?数字不会凭空消失,一定是做了某种计算后,原本有 $n$ 位有效数字,结果变成了小于 $n$ 位,这就是缺失,而缺失不可能发生在右侧,也就是 $d_n$ 这一侧,原因是,如果这些位消失了,我们依然可以补充0作为有效数字,所以我们要注意左侧的数字。
比如:
$$
\begin{aligned}
&123.4567\\
-&123.4566\\
&\hline\\
=&000.0001
\end{aligned}
$$

这时候,我们原本的7位有效数字,瞬间变成1位,有问题么?没有问题,因为这是准确结果,但是从数值上看,有效数字减少了。
但是,如果减法的两个操作数,减数或者被减数,并不是一个确定的数,而是一个表达式,包含无限的小数呢?

“减法”=“陷阱”

考虑 $\sqrt{9.01}-3$ 这个表达式的值。
这个例子很简答,输入matlab能够直接得到答案,$\sqrt{9.01}\approx 3.001 662$ 所以 $\sqrt{9.01}-3\approx |0.001 662$ 减法之前有七位有效数字,结果有4位有效数字,也就是少了3位,这看起来没啥。
然后我们假定,我们有一种特殊的计算机,其浮点数最多只能保存十进制的3位有效数字,(书中说是三位计算机,表达不是很准确,会和三个二进制位混淆,所以我们说这个计算机只能计算3位十进制)那么我们如果按照上面的计算过程,那么会得到:
$$
\sqrt{9.01}\approx 3.001 662=3.00\\
\sqrt{9.01}-3=0
$$
结果是0,第一步我们把3.001662保存成3.00 的原因是计算机只能保存3位十进制数,所以这个结果是截断误差造成的,然后相减得到了|0.00,理想情况,有效数字是1.66,可见一个都不对。
我们这里所说的有效数字是和真实计算结果数字上的比较,不是比较大小,只考虑尾数,不考虑指数。
两个相近的数相减,可能会造成有效数字的缺失
但是你可能说,没办法,这是机器的限制,你那个能存一百位有效数字的计算机来就可以了,如果你这么想,那就没得聊了,
有没有办法?当然有办法!

解决方法

解决办法的核心思想是:把相近的两个数变成不相近的!
同样使用只有三维有效数字的计算机
$$
\sqrt{9.01}-3=\frac{(\sqrt{9.01}-3)(\sqrt{9.01}+3)}{\sqrt{9.01}+3}=\frac{|0.01}{3.00+3}=\frac{1}{6}=1.67\times 10^{-3}
$$
上面这种方法叫做共轭等式,可以避免两个相近的数字相减。下面我们看另一个例子,主要技巧也是用一些恒等式:
$$
E_1=\frac{1-\text{cos}x}{\text{sin}^2x}
$$
明显得当$x\to 0$ 的时候 $\text{cos}x\to 1$ 那么就有可能造成有效数字缺失,与上面类似的,我们也有共轭等式:
$$
E_2=\frac{(1-\text{cos}x)(1+\text{cos}x)}{\text{sin}^2x(1+\text{cos}x)}=\frac{1}{(1+\text{cos}x)}=E_1
$$
然后我们就写个代码来观察下 $E_1$ 和 $E_2$ 在 $x\to 0$ 的时候的表现,是否 $E_2$ 更准确,我们写一段python程序:

1
2
3
4
5
6
7
8
9
import math
x=1.0
for i in range(0,16):
x=x/10
cos_x=math.cos(x)
sin_x=math.sin(x)
e1=(1- cos_x)/pow(sin_x,2)
e2 = (1 ) / (1 + cos_x)
print '|%1.16f|%1.16f|%1.16f|' % (x,e1,e2)

运行结果是:

$x$ $E_1$ $E_2$
0.1000000000000000 0.5012520862885769 0.5012520862885712
0.0100000000000000 0.5000125002084805 0.5000125002083363
0.0010000000000000 0.5000001249921894 0.5000001250000208
0.0001000000000000 0.4999999986279311 0.5000000012500000
0.0000100000000000 0.5000000413868522 0.5000000000125000
0.0000010000000000 0.5000444502913370 0.5000000000001250
0.0000001000000000 0.4996003610813219 0.5000000000000012
0.0000000100000000 0.0000000000000000 0.5000000000000000
0.0000000010000000 0.0000000000000000 0.5000000000000000
0.0000000001000000 0.0000000000000000 0.5000000000000000
0.0000000000100000 0.0000000000000000 0.5000000000000000
0.0000000000010000 0.0000000000000000 0.5000000000000000
0.0000000000001000 0.0000000000000000 0.5000000000000000
0.0000000000000100 0.0000000000000000 0.5000000000000000
0.0000000000000010 0.0000000000000000 0.5000000000000000
0.0000000000000001 0.0000000000000000 0.5000000000000000

从 0.00000001开始 $E_1$ 崩溃,而 $E_2$ 保持稳定。
到0.00000001崩溃的原因是cos在这个位置截断后就是1.0:

re-2

同样,我们这套方法的核心就是不要见相近的数字,只要有减法就要小心,注意前后两个数是否相近,另一个例子是二次方程的求解,对于公式法我们必须要计算:
$$
x=\frac{-b\pm\sqrt{b^2-4ac}}{2a}
$$
其中要注意的是
$$
x_1=\frac{-b+\sqrt{b^2-4ac}}{2a}
$$
对应的方法就是把这个计算改成:
$$
x_1=\frac{(-b+\sqrt{b^2-4ac})\cdot(+b+\sqrt{b^2-4ac})}{2a(+b+\sqrt{b^2-4ac})}=\frac{-4ac}{2a(+b+\sqrt{b^2-4ac})}=\frac{-2c}{(b+\sqrt{b^2-4ac})}
$$
当然这里面还有问题就是 $b^2-4ac$ 的部分依然存在减法
而且 $b$ 是负数的时候,依然会存在 $b+\sqrt{b^2-4ac}$ 相近的情况,尤其是 $ac<<b^2$ 的时候,这时候计算最好使用:
$$
x_1=\frac{-b+\sqrt{b^2-4ac}}{2a}\\
x_2=\frac{2c}{-b+\sqrt{b^2-4ac}}
$$

总结

有效数字可能会带来很小的误差,也可以带来很大的误差,所以,不管什么时候,我们能避免减法就避免减法,尤其减法的两个数字接近的时候,这个时候更危险。

0%