2.4.1 二进制小数

2.4.1 二进制小数

理解浮点数的第一步是考虑含有小数值的二进制数字。首先,让我们来看看更熟悉的十进制表示法。十进制表示法使用如下形式:

dmdm-1...d1d0.d-1d-2...d-n

其中每个十进制数 di 的取值范围是 0~9。这个表达描述的数值 d 定义为:

d = Σi=-nm 10i × di

数字权的定义与十进制小数点符号(.)相关,这意味着小数点左边的数字的权是 10 的正幂,得到整数值,而小数点右边的数字的权是 10 的负幂,得到小数值。例如,12.3410 表示数字 1 × 101 + 2 × 100 + 3 × 10-1 + 4 × 10-2 = 12 34/100。

类似,考虑一个形如

bmbm-1...b1b0.b-1b-2...b-n+1b-n

的表示法,其中每个二进制数字,或者称为位,bi 的取值范围是 0 和 1,如图 2-31 所示。这种表示方法表示的数 b 定义如下:

b = Σi=-nm 2i × bi (2.19)

符号 . 现在变为了二进制的点,点左边的位的权是 2 的正幂,点右边的位的权是 2 的负幂。例如,101.112 表示数字 1 × 22 + 0 × 21 + 1 × 20 + 1 × 2-1 + 1 × 2-2 = 4 + 0 + 1 + 1/2 + 1/4 = 5 3/4。

图 2-31 小数的二进制表示

从等式 (2.19) 中可以很容易地看出,二进制小数点向左移动一位相当于这个数被 2 除。例如,101.112 表示数 5 3/4,而 10.1112 表示数 2 + 0 + 1/2 + 1/4 + 1/8 = 2 7/8。类似,二进制小数点向右移动一位相当于将该数乘 2。例如 1011.12 表示数 8 + 0 + 2 + 1 + 1/2 = 11 1/2。

注意,形如 0.11...12 的数表示的是刚好小于 1 的数。例如,0.1111112 表示 63/64,我们将用简单的表达法 1.0 - ε 来表示这样的数值。

假定我们仅考虑有限长度的编码,那么十进制表示法不能准确地表达像 1/3 和 5/7 这样的数。类似,小数的二进制表示法只能表示那些能够被写成 x × 2y 的数。其他的值只能够被近似地表示。例如,数字 1/5 可以用十进制小数 0.20 精确表示。不过,我们并不能把它准确地表示为一个二进制小数;我们只能近似地表示它,增加二进制表示的长度可以提高表示的精度:

表示 十进制
0.02 0/2 0.010
0.012 1/4 0.2510
0.0102 2/8 0.2510
0.00112 3/16 0.187510
0.001102 6/32 0.187510
0.0011012 13/64 0.20312510
0.00110102 26/128 0.20312510
0.001100112 51/256 0.1992187510

练习题 2.45 填写下表中的缺失的信息:

小数值 二进制表示 十进制表示
1/8 0.001 0.125
3/4
25/16
10.1011
1.001
5.875
3.1875

练习题 2.46 浮点运算的不精确性能够产生灾难性的后果。1991 年 2 月 25 日,在第一次海湾战争期间,沙特阿拉伯的达摩地区设置的美国爱国者导弹,拦截伊拉克的飞毛腿导弹失败。飞毛腿导弹击中了美国的一个兵营,造成 28 名士兵死亡。美国总审计局(GAO)对失败原因做了详细的分析 [76],并且确定底层的原因在于一个数字计算不精确。在这个练习中,你将重现总审计局分析的一部分。

爱国者导弹系统中含有一个内置的时钟,其实现类似一个计数器,每 0.1 秒就加 1。为了以秒为单位来确定时间,程序将用一个 24 位的近似于 1/10 的二进制小数值来乘以这个计数器的值。特别地,1/10 的二进制表达式是一个无穷序列 0.000110011[0011]...2,其中,方括号里的部分是无限重复的。程序用值 x 来近似地表示 0.1,x 只考虑这个序列的二进制小数点右边的前 23 位:

x = 0.000110011001100110011002

(参考练习题 2.51,里面有关于如何能够更精确地近似表示 0.1 的讨论。)

A. 0.1 - x 的二进制表示是什么?

B. 0.1 - x 的近似的十进制值是多少?

C. 当系统初始启动时,时钟从 0 开始,并且一直保持计数。在这个例子中,系统已经运行了大约 100 个小时。程序计算出的时间和实际的时间之差为多少?

D. 系统根据一枚来袭导弹的速率和它最后被雷达侦测到的时间,来预测它将在哪里出现。假定飞毛腿的速率大约是 2000 米每秒,对它的预测偏差了多少?

通过一次读取时钟得到的绝对时间中的轻微错误,通常不会影响跟踪的计算。相反,它应该依赖于两次连续的读取之间的相对时间。问题是爱国者导弹的软件已经升级,可以使用更精确的函数来读取时间,但不是所有的函数调用都用新的代码替换了。结果就是,跟踪软件一次读取用的是精确的时间,而另一次读取用的是不精确的时间 [103]。