数的存储
与整数的存储相比,带小数部分的数值存储要求不仅包括该数的二进制表示形式的0和1的位模式,而且包括二进制小数点的位置。对此有一种通行的基于科学记数法的方法,叫做浮点记数法。
(1) 浮点记数法
下面用一个字节存储的例子来说明浮点记数法,8位格式是实际系统的代表,并且用于示范重要的概念。
首先指定最高位是符号位,符号位为0表示存储的值是非负数;为1表示的值为负数。其次,把其余的7位划分成两组,或是叫字段(fields),阶码字段和尾数字段。指定符号位右边的3位为阶码字段,最后余下的4位是尾数字段。图2.25显示了字段划分的情况。
图2.25浮点记数法的组成
通过下面的例子,说明各字段的含义。假设字节中包含的位模式是0 1 1 0 1 0 1 1,用上述规定的格式来分析该模式。我们看到,符号位为0,阶码是1 1 0,尾数是1 0 1 1。为解码该字节,首先提取尾数并把小数点放在它的左边,得到:
. 1 0 1 1
接下来提取阶码字段内容(1 1 0),把它当作是3位余码表示形式,所以该阶码字段内容表示的是+2,这就告诉我们要将上步所得的解的小数点向右移动两位(负的阶码意味小数点向左移动)。最终,得到的解是:
1 0 .1 1
它是23〖〗4的二进制表示形式。其次,例子中符号位为0,因此,代表的值是非负数。得出结论是:字节0 1 1 0 1 0 1 1表示的是23〖〗4。
再举另外一个例子,字节1 0 1 1 1 1 0 0。提出尾数得到:
.1 1 0 0
由于阶码字段(0 1 1)表示值是-1。因此,其解为:
.0 1 1 0 0
它表示3/8。由于最初的位模式的符号位是1,存储的值是负的,所以得出结论是:模式10111100表示-3/8。
使用浮点记数法存储一个数值,可以把上述过程颠倒过来。例如,对11〖〗8编码,首先采用二进制形式表示它,得到1.001。接着,从左向右把它拷贝到尾数字段中,并使尾数最左边的位为1。至此,字节是:
1001
现在必须填写阶码字段。要设想小数点在尾数的左边时尾数的内容以及要决定小数点移动位数和方向,以求得到原始的二进制数。在例子中,我们知道.1001中的小数点向右移动1位,从而得到1.001。所以阶码应该为+1。于是把+1的3位余码的形式101,放入阶码字段中,再用0填写符号位。原因就是要存储的值是非负数。最后完成的字节是这样的:
01011001
在填写尾数字段时,填写的规则是:对原数值的二进制表示形式的位模式,从最左边的1开始,从左至右地向尾数字段拷贝。为了说明这一点,假设存储3/8的过程,它的二进制记数形式是.011。在这种情形下,尾数应为:
1100
而不应为:
0110
其原因是填写尾数字段是从其二进制表示形式(.011)的最左边的1开始。这个规则消除了对于相同的值的多重表示形式的可能性。同时意味着所有非零值的表示形式会有一个以1开始的尾数。这种表示形式称之为规范化格式。但是注意到,0值一定是个特殊情况,其浮点表示形式是全0的位模式。
(2) 舍位错误
如果试图用一个字节的浮点系统存储25〖〗8,写成二进制形式10.101。但当填写尾数字段时,超出了尾数字段范围,丢失了最右边的1,尾数字段填为1010。
如果忽略这个问题并且继续填写阶码字段和符号位,最后得到位模式01101010,它表示21〖〗2并非是25〖〗8。这里发生的叫做舍位错误,意思是由于尾数字段的长度不足致使待存储的数值部分地丢失。
通过使用更长的尾数字段可以减少这种错误。像整数存储一样,现在普遍使用至少32位浮点记数法存储数值,而不是这里所使用的8位。这种方法同时也允许使用更长的阶码字段。即使采用这些更长的格式,当要求更高的精度时,仍然存在着倍数问题。
舍位错误的另一个来源是在十进制中已经习以为常的现象——无限制展开问题,就像在用十进制形式表示1/3时见到的那样,某些值不管用怎样多的数字位都不可能准确地表示。常规的十进制记数法与二进制记数法之间的差别是二进制中有比十进制存在更多数值有无终止表示形式。例如1/10在十进制中可以精确表示,可在二进制中是无终止的。想象一下,一个粗心的人采用浮点记数法去存储和处理元和分的钱数。特别是,以元作为度量单位,那么角的值就不能准确存储。这种情况的解决办法是采用以分为单位进行处理数据,这样所有的数值都是可以用二进制阶码准确存储的整数。
舍位错误及其相关问题是数值分析领域里的专家们每天都关注的问题。这个数学分支处理的是关于大容量和高精度的实际计算中的问题。
下面的例子可以作一个简单的说明。假设,要求对使用指定的一个字节浮点记数法表示的三个数进行加法运算:
21〖〗2+1〖〗8+1〖〗8
如果我们按列出的顺序进行相加,首先加21〖〗2和1/8并得25〖〗8,它的二进制形式是10.101,不幸的是这个值不能被准确地存储,作为第一步得到的存储结果却是01101010,它所实际表示的值是21〖〗2(这正像我们在前面曾经做过的例子一样)。下一步将这个结果加最后一个1/8,这里再一次发生舍位的错误,并且得出最后的结果将是一个错误的答案21〖〗2。
现在按反向的次序进行相加。首先做1/8加1/8得到1/4。二进制形式是.01;那么,第一步的结果存入一个字节为00111000,它是准确的。现在,按次序把1/4与下个值21〖〗2相加,并且得23〖〗4,该值可以准确地存储到一个字节中,模式是01101011。这时的结果是正确的答案。
总之,在数值相加时,它们相加的次序可能是重要的。问题是,如果一个很大的数加一个非常小的数,那么小的数可能会被舍去。所以,对于多个数值的相加,其规则是先把较小的数加在一起,希望它们累加成的值在与较大数相加时是有意义的。