计算机系统表示带有小数部分的数字通常有两种方式:定点表示和浮点表示。
在CPU还不支持硬件浮点运算的时代,程序员编写需要处理小数的高性能软件时经常使用定点运算方式。与浮点格式相比,定点格式支持小数所需的软件开销更少。但是,后来CPU厂商在CPU中加入了支持硬件浮点运算的FPU,如今已经很少有人使用CPU进行定点运算了。使用CPU原生的浮点格式开销往往更低。
尽管CPU厂商一直在努力优化系统中的浮点算法,但在某些情况下,精心编写的定点计算汇编语言程序会比等效的浮点运算代码运行得更快。例如,某些3D游戏程序使用的16:16(16位整数,16位小数)格式比32位浮点格式运算更快。正是因为在这类场景中使用定点运算十分有效,所以本节就讨论定点表示形式和使用定点格式的小数。
注意: 浮点格式在第4章讨论。
前面我们提到,位值计数系统使用小数点右边的数值来表示小数(0~1之间的值)。在二进制计数系统中,二进制小数点右边的每一位代表值0或1乘以2的负次幂,负次幂按位递减。数值的小数部分使用二进制小数之和来表示。例如,数值5.25用二进制值101.01表示。十进制的转换方法如下:
1×2 2 +1×2 0 +1×2 -2 =4+1+0.25=5.25
使用定点二进制格式时,可以在二进制表示形式中选择一个位,将二进制小数点隐含地放置在该位之前。二进制小数点的位置可以根据数字小数部分需要的有效位数来选择。例如,如果数值整数部分表示的范围在0~999之间,那么二进制小数点的左边至少需要10位才足够表示此范围内的数值。如果是有符号的数值,还需要预留额外一位给符号。如果采用32位定点格式,则小数部分需要保留21位或22位,具体取决于数值是否有符号。
定点数只能表示实数的一小部分。而任意两个整数值之间的实数有无限多个,因此定点数无法精确地表示每一个数(这需要无限多个位)。大多数实数采用定点表示时都要取近似值。以8位定点格式为例,如果其中6位表示整数部分,其他2位表示小数部分,则整数部分可以表示0~63的值(或-32~31的有符号值),小数部分只能表示4个不同的值:0.0、0.25、0.5和0.75。这种格式无法精确地表示1.3。最好的办法是选择最接近的近似值(1.25)。这会带来误差。可以增加定点格式中二进制小数点右边的位数来减少这种误差(代价是整数部分可以表示的范围缩小或定点格式的位数增多)。例如,如果使用8位整数部分和8位小数部分的16位定点格式,则二进制数值1.01001101近似等于数值1.3。十进制等式如下:
1+0.25+0.03125+0.15625+0.00390625=1.30078125
定点格式的小数部分位数越多近似值就越接近真实值(使用上面这种定点格式的误差仅为0.00078125,相比之下前面一种定点格式的误差为0.05)。
无论定点格式的小数部分有多少位,总有一些数值是定点二进制计数系统永远都无法精确表示的(1.3 恰好就是这样的数值)。这可能就是人们(错误地)认为十进制运算比二进制运算更精确的主要原因(尤其是在处理0.1、0.2、0.3等十进制小数的时候)。
我们用定点十进制系统(采用BCD表示形式)来比较两种系统的精度。假设选择16位格式,整数部分和小数部分分别为8位,整个格式可以表示0.0~99.99的十进制数,精确到小数点后两位。使用BCD表示法,可以用$0130这样的十六进制值来精确地表示数值1.3(小数点隐含在第二和第三位数字之间)。如果在计算中使用的都是0.00~0.99的小数,则BCD表示比(使用8位小数部分的)二进制定点表示更精确。
但是,总的来说二进制格式更精确。二进制格式可精确地表示256个不同的小数,而BCD格式只能表示100个小数。随机选择一个小数,二进制定点表示比十进制格式提供更精确的近似值(因为二进制能表示的小数是十进制的2.5 倍)。(可以比较位数更多的格式:以16位的小数为例,十进制/BCD定点格式可以精确到小数点后四位,可以表示 10000个小数;而二进制格式可以表示65,536个小数,精度是十进制可以表示的6倍还要多。)十进制定点格式的优势仅限于可以准确表示日常使用的小数。在美国,货币计算通常会产生这种小数,因此程序员认为十进制格式更适合货币计算。但是,对于大多数金融计算要求的精度(通常小数点后四位是最低要求),二进制格式往往才是最好的选择。
如果一定要使用两位以上的精度来准确表示0.00~0.99的小数,则二进制定点格式不是可行的解决方案。但十进制格式也不是唯一的选择。我们很快就会看到,还有其他二进制格式可以准确地表示这些数值。