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
9import 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:
同样,我们这套方法的核心就是不要见相近的数字,只要有减法就要小心,注意前后两个数是否相近,另一个例子是二次方程的求解,对于公式法我们必须要计算:
$$
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}}
$$
总结
有效数字可能会带来很小的误差,也可以带来很大的误差,所以,不管什么时候,我们能避免减法就避免减法,尤其减法的两个数字接近的时候,这个时候更危险。