2.6 浮点数
介绍了整数之后,下一步就是讨论浮点运算,即实数之间的运算。实数是所有有理数和无理数的集合。浮点运算能够让人们处理科学应用(与金融或商业应用相对)中很大的和很小的数。浮点运算不像整数运算,它的计算结果一般是不确定的。一块芯片上的浮点计算结果也许与另一块芯片上的不同。后面将解释为什么浮点运算无法获得确定的答案,并讨论一些程序员必须了解的陷阱。
n位字长的计算机能够处理值为0~2n-1的单字长无符号整数。更大的整数可以通过将多个字链接在一起来表示。例如,一台32位的计算机可以将两个32位字拼接在一起以处理64位数(一个表示64位数的高半部分,另一个表示它的低半部分)。科学家和工程师经常会处理值范围极大的数(例如,从电子的质量到星体的质量)。这些数被表示为浮点数并进行处理,之所以这样叫是因为小数点在数中的位置并不是固定的。一个浮点数值分两部分存储:数值以及小数点在数值中的位置。
浮点数表示也被称作“科学计数法”,因为科学家用它来表示很大或很小的数。1.2345×1020,0.4599×10-50,-8.5×103等都是十进制浮点数。在十进制运算中,科学计数法表示的数字被写成尾数×10指数的形式,这里尾数表示这个数,而指数则以10的整数幂为倍数将其扩大或缩小。
二进制浮点数则被表示为尾数×2指数的形式。例如,101010.1111102可被表示为1.01010111110×25,这里尾数为1.01010111110,指数为5(用8位二进制数表示为00000101)。今天,术语mantissa(尾数)已被替换为significand以表示浮点数中有效位的位数。由于浮点数被定义为两个值的积,浮点数的表示并不唯一;例如10.110×24 = 1.0110×25。
多年以来,计算机系统使用了很多不同的方法表示浮点数的尾数和指数。20世纪70年代,一种标准的浮点数表示方法快速地取代了大多数系统自有的格式。IEEE 754浮点数标准提供了3种浮点数表示:32位单精度浮点数,64位双精度浮点数,以及128位四精度浮点数。
1.规格化浮点数
IEEE 754浮点数的尾数总是规格化的(除非它等于0),其范围为1.000…0×2e到1.111…1×2e,这里e为指数。规格化浮点数的最高位总是1。规格化使尾数的所有位都是有效的,因而尾数的精度最高。若某个浮点计算的结果为0.110…×2e,它应被规格化为1.10…×2e-1。同样,结果10.1…×2e应被规格化为1.01…×2e+1。
尾数规格化充分利用了可用的最大精度。例如,一个8位非规格化的尾数0.0000101只有4个有效位,而规格化后的8位尾数1.0100011则有8个有效位。
2.偏置指数
IEEE 754浮点数的尾数被表示为符号和数值的形式,即用一个符号位表示它是正数还是负数。它的指数则用偏置方式表示,即给真正的指数加上一个常数。假定所用的指数为8位,偏置值为127。若一个数的指数为0,它被保存为0 + 127 = 127。若指数为-2,则被保存为-2 + 127 = 125。实数1010.1111规格化的结果为+1.010111×23。指数为+3,将被保存为3 + 127 = 130;即13010或用二进制表示为10000010。
这种用偏置表示指数的方法的优点在于,最小的负指数被表示为0。若不采用这种方法,0的浮点表示为0.0…0×2最小负指数。采用偏置指数之后,0就可以用尾数0和指数0表示,如图2-6所示。
2.6.1 IEEE浮点数
一个32位IEEE 754单精度浮点数可以被表示为下面的二进制位串:
S EEEEEEEE 1.MMMMMMMMMMMMMMMMMMMMMMM
这里S为符号位,指明这个数是正数还是负数;E为8位偏置指数,指出了小数点的位置;M是23位小数尾数。细数这个数,我们会发现它有33位而不是32位,这是因为当这个数被保存在存储器中时,尾数最前的那个1被省掉了。只有用M表示的尾数的小数部分才会被存入存储器中(后面将很快介绍这样做的原因)。图2-7描述了32位浮点数的结构。
S位为符号位,决定了数的符号。若S = 0,该数为正数,若S = 1,则该数为负。指数E将浮点数的尾数扩大或缩小2的E次方倍,并且它的偏置值为127。例如浮点数+1.11001…0×212的指数为12 + 127 = 13910 = 100010112。
IEEE浮点数的尾数总是规格化的,其值在1.0000…00至1.1111…11之间,除非这个浮点数是0,此时尾数为0.000…00。由于尾数总是规格化的,且它的最高位总是1,因此将尾数存入存储器时没有必要保存最高位的1。所以,一个非0的IEEE 754浮点数可被定义为:
X = -1S × 2E-B × 1·F
式中:
S = 符号位;0 = 正尾数;1 = 负尾数;E = 偏置量为B的指数;F = 尾数的小数部分(注意实际尾数为1·F,有个隐含的1)
前面已经提到,浮点数0应被表示为S = 0,E = 0,M = 0(即浮点数0用全0表示)。
IEEE浮点数被称为字典序的,因为无论两个数是被当作浮点数还是有符号整数,它们的大小顺序都是相同的。这一特性意味着人们可以用简单的逻辑电路比较两个浮点数的大小,电路的复杂度与浮点表示的复杂度无关。
请考虑下面的例子:如何将一个32位IEEE单精度浮点数X解压缩为一个符号位、一个偏置指数和一个尾数。X = 11000001100110011000000000000000。解压缩这3个字段得到
S = 1,E = 10000011,且F = .00110011000000000000000
为了得到实际的尾数,当解压缩尾数时我们会在所保存的尾数的小数部分之前增加一位,得到1. 00110011000000000000000。因此这个数等于
-1.00110011000000000000000×210000011-01111111 = -1. 00110011000000000000000×24 = -10011.0011。
- IEEE浮点数格式
ANSI/IEEE 754-1985标准定义了基本的和扩展的浮点数格式,以及一组数量有限的算术运算的规则(加、减、乘、除、平方根、求余和比较)。
非数(Not a Number, NaN)是IEEE 754标准的一个重要概念。NaN是IEEE 754标准提供的一个专门符号,代表IEEE 754标准格式所不能表示的数。NaN的使用和定义是与系统相关的,可以用NaN来表示所需要表达的任何信息。
IEEE 754标准定义了3种浮点数格式:单精度、双精度和四精度(见表2-7)。在32位IEEE 754单精度浮点数格式中,最大指数Emax为+127,最小指数Emin为-126,并不是我们所想的+128~-127。Emin-1(即-127)用来表示浮点0,Emax+1用来表示正/负无穷大或NaN数。
解压缩浮点数时,浮点数指数和尾数的位数都会增加,这种格式被称为扩展格式。通过格式扩展,浮点数的表示范围和精度都增加了。例如,解压缩一个32位浮点数时,增加起始位1可将23位的尾数小数部分扩展到24位,然后尾数被扩展为32位(要么作为一个32位字,要么作为两个16位字)。接下来的所有运算都在32位扩展精度的尾数上进行。扩展格式上的运算完成之后,运算结果按照原先的格式重新压缩并保存在存储器中。
最小指数的绝对值小于最大指数的绝对值,以确保计算最小数的倒数时不会产生上溢。当然,反过来就意味着最大数的倒数会产生下溢。不过下溢带来的问题没有上溢那么严重。
图2-8描述了IEEE单精度浮点数格式。指数E=0和E=255等特例分别被用于表示浮点0、非规格化小数、正或负无穷大,以及NaN数等。
- IEEE浮点数的特点
首先来看看浮点数的一些不明显的特点。请考虑两个浮点数F1和F2的差,这两个数只有最低位不同;即
d=F2-F1=e2x1. f2-e1×1. f1=2e2-b×1. f2-2e2-b×1. f1=2e-b×0.000,..,1
这两个数的差是2-p×2e-b,这里p是尾数的位数,b是指数e的偏置常数。当指数e的值很大时,两个连续的浮点数的差也很大;而当b的值很小时,差也很小。
浮点数的另一个特点与接近0时的情形有关。图2-9描述了一个指数为2位,尾数为2位的浮点数系统。浮点数0表示为00 000。下一个规格化的正数表示为00 100(即2-b×1.00,这里b为偏置常数)。
图2-9说明,浮点数0附近有一块禁止区,其中的浮点数都是非规格化的,因此无法被表示为IEEE标准格式。这个数的指数和起始位都是0的区域,也可用来表示浮点数。不过,这些数都是非规格化的,其精度比规格化数的精度低,会导致渐进式下溢。