我们知道,在编写程序时,两个浮点数(float
或double
)不能直接进行大小比较。
当然,我们也都知道,不能直接进行大小比较的原因是因为浮点数在计算机内部不能精确的表示。可是,为什么在计算机内部浮点数不能够精确地表示呢?
这还得从IEEE 754标准说起。
根据IEEE 754标准,浮点数在计算机内部存储时主要分为符号位(sign)、指数(exponent)部分、尾数(fraction)部分:
那么,这个数就是:
例如十进制的0.5可以表示成 (−1)0×2−1×1.0 。
以32位的单精度浮点数(常见的float
)为例,它的符号位占一位(bit),指数部分占8位,尾数部分占23位。
现在我们需要存储十进制的0.1这个小数,首先需要把0.1转换成二进制数。然而,我们会发现,十进制的0.1转换成二进制是一个无限循环小数:0.0001100110011001100…
可是,尾数只有23位,只能截取二进制小数的前23位存储。这时,误差就产生了。
当再次把这个浮点数转换成十进制数时,由于损失了一些二进制位,转换回来的十进制数自然也就与原来的不同了。
即使是有11位指数和52位尾数的64位双精度浮点数,也不可能精确存储一个无限循环小数,只能是相对单精度浮点数而言,存储的精度更高。
看懂IEE 754标准之前,首先需要了解一下定点数的表示。
所谓定点数,就是小数点位置固定不动的小数。
例如二进制的0.10可以表示成10。
虽然计算机不能表示出小数点,但是这里我们假设小数点就在最前面,于是,任何小于1且大于等于0的小数都可以用定点数来表示。
例如:001表示的是二进制小数0.001。
相对于定点数,小数点的位置不固定的数就叫做浮点数。由于浮点数的小数点位置不固定,因此,就需要一定的方法来表示小数点的位置。
现在的计算机主要采用的是IEEE 754标准来表示浮点数。
事实上,在浮点数的存储过程中,为了运算方便和尽可能多的提高存储存储空间的利用效率,往往不会直接存储各部分的二进制原码。
例如,指数部分存储的是实际指数值得移码,而尾数部分存储的是规约化后的尾数的补码。
所谓移码,就是将实际的指数值加上一个固定的数值而得到的数。在单精度浮点数中这个数是127,在双精度浮点数中这个数是1024。假设这个数是e,则e的计算公式如下:
其中 n 是指数部分的长度。
如此一来,即使是最小的指数(例如单精度浮点数是-126),在存储的时候也会存储为1(-126+127)。这样,就方便了指数的大小比较也省了一个符号位。
现在,上面的浮点数的计算公式就变成了下面的样子:
当然,这还不是计算公式的最终形态,因为我们还有尾数没有说。
刚刚在上面提到过定点数的表示其实就是在这里做一下铺垫。因为,尾数部分实际上存储的就是一个定点数。
这里的定点数需要时一个以1开头的二进制数并且小数点位于第一位数的后面。
拿 (0.5)10 来说,把它转换成二进制是 (0.1)2=(−1)0×(1.0)2×2−1
把指数部分用移码表示就是 −1+127=126 ,所以,0.5的就存储为:
0 01111110 1000000000000000000000
现在我们又发现,既然尾数部分的定点数都是1开头,那就把这个1给省略掉好了。这样就又多出一位来保存尾数,提高精度了(当然,省略开头的第一个1后,默认小数点的位置就变成现在的第一位数之前了)。
于是乎,上面的0.5在实际存储中就变成了
0 01111110 0000000000000000000000
此时,最终形态的计算公式就是:
当指数部分为0,而尾数部分不为0(指数、尾数同时为0表示的当然是数字0)时,这个浮点数称为非规约形式浮点数。IEEE 754标准规定:非规约形式的浮点数的指数偏移值比规约形式的浮点数的指数偏移值大1。
(完)