3.2 数字数据表示法
数值是计算机系统最常用的数据类型。与其他数据类型不同的是,不必把数字数据映射到二进制代码。因为二进制也是一种记数系统,所以在数字数据和计算机存储的表示它们的二进制数值之间有种自然对应的关系。通常对正整数来说都是这样的。在第2章关于二进制系统和其他等价记数系统的讨论中,我们介绍了整数转换的问题。但是,还有其他关于数字数据表示法的问题需要考虑,整数不过是数字数据的一部分。这一节将讨论负数和非整数数值的表示法。
3.2.1 负数表示法
负数只是前面带有负号的数吗?也许吧。这当然是看待负数的有效方式之一。让我们来探讨关于负数的问题,讨论在计算机上表示负数的适当方式。
符号数值表示法
从初次在中学学习负数开始,你就使用过数的符号数值表示法。在传统的十进制系统中,数值之前带有符号(+或-),只不过正号通常被省略。符号表示了数所属的分类,数字表示了它的量值。标准的实数直线图如下,其中负号表示该数位于0的左侧,正数位于0的右侧。
符号数值表示法(signed-magnitude representation):符号表示数所属的分类(正数或负数)、值表示数的量值的数字表示法。
对带符号的整数执行加法和减法操作可以被描述为向一个方向或另一个方向移动一定的数字单位。要求两个数的和,即找到第一个数的刻度,然后向第二个数的符号所示的方向移动指定的数字单位。执行减法的方式一样,即按照符号所示的方向沿着实数直线图移动指定的单位。在中学,即使不使用实数直线图,你也能够很快掌握加法和减法运算。
符号数值表示法有一个问题,即表示0的方法有两种:一种是+ 0,一种是- 0。我们不会对- 0感到迷惑,忽略它即可。但是,在计算机中,0的两种表示法却会引起不必要的麻烦,所以还有其他表示负数的方法。让我们来分析另一种负数表示法。
定长量数
如果只允许用定量的数值,那么可以用一半数表示正数,另一半数表示负数,符号由数的量值决定。例如,假定能够表示的最大十进制数是99,那么可以用1到49表示正数1到49,用50到99表示负数- 50到- 1。这种表示法的实数直线图如下所示,它标示了上面的数对应的负数。
在这种模式下执行加法,只需要对两个数求和,然后舍弃进位即可。求两个正数的和应该没有什么问题,让我们来尝试求一个正数加一个负数、一个负数加一个正数以及两个负数相加。下表分别列出了用符号数值表示法和用这种模式执行的加法运算。(注意,进位被舍弃了。)
符号数值表示法 新模式
?????5
- ?- 6
???- 1 ????5 - ?94
???99
???- 4
- ???6
?????2 ???96 - ??6
????2
???- 2
- ?- 4
???- 6 ???98 - ?96
???94
用这种模式表示的负数的减法运算又如何呢?关键是加法和减法之间的关系,即A - B=A + (- B)。从一个数中减去另一个数,等价于给第一个数加上第二个数的负数。
符号数值表示法 新模式 加负数
??- 5
- ??3
??- 8 ???95 - ??3
????95
- ??97
????92
在这个例子中,我们假定只有100个数值,这个数量非常小,使我们能够用实数直线图来计算一个数的负数(Negative)表示法。不过,要计算负数表示法,可以采用下列公式。
Negative (I) = 10k - I,其中k是数字个数
在两位数字表示法中,求- 3的表示法的公式如下:
- (3) = 102 - 3 = 97
在三位数字表示法中,求- 3的表示法的公式如下: - (3) = 103- 3 = 997
这种负数表示法称为十进制补码。虽然人类以符号和量值表示数字,但在电子计算中,补码在某些方面更方便。由于现代计算机存储任何数据采用的都是二进制,所以我们采用与十进制补码等价的二进制补码。
十进制补码(ten′s complement):一种负数表示法,负数I用10的k次幂减I表示。
二进制补码
假定数字只能用八位表示,七位表示数值,一位表示符号。为了便于查看长的二进制数,我们把实数直线图绘制成垂直的。
如果将十进制替换为二进制,那么补码公式还会有效吗?也就是说,我们能不能用公式“Negative(I) = 2k - I”来计算用负二进制表示的数值呢?让我们尝试看看:
- (2) = 27 - 2 = 128 - 2 = - 126
十进制数126用八进制表示是176,用二进制表示是11111110,但是左边多了一个位数“1”。是出错了吗?并不是,因为这是一个负数,最左边的位数表示了这个数字是负数还是正数。如果最左边一位是“0”,那么说明这个数字是正数;如果是“1”说明这个数字是负数。因此“- 2”表示为“11111110”。
有一个更简单的方法来计算二进制补码:将每一位取反再加一。也就是,取数字的正值,将所有“1”变成“0”,将所有的“0”变成“1”,再加1。
使用十进制补码计算加法和减法的方式和二进制补码是一样的:
使用这种表示法,负数的最左边一位总是1。因此,在二进制补码中,你可以立刻识别出一个数是正数还是负数。
数字溢出
当我们分配给结果的位数存不下计算出的值时,将发生溢出。例如,如果使用八位来存储每个值,那么127加3的结果将溢出:
溢出(overf?low):给结果预留的位数存不下计算出的值的状况。
在我们的模式中,10000010表示- 126,而不是 + 130。但是,如果表示的不是负数,这个结果将是正确的。
溢出是把无限的世界映射到有限的机器上会发生的典型问题。无论给一个数字分配多少位,总有潜在的表示这些位不能满足的数的需要。对于如何解决溢出问题,不同的计算机硬件和不同的程序设计语言有自己独特的方法。
3.2.2 实数表示法
在计算中,我们把非整数的值称为实值。根据实数在计算机中的用途,把它定义为可能具有小数部分的值。也就是说,实数具有整数部分和小数部分,每个部分都可能是0。例如,104.32、0.999?999、357.0和3.141?59都是十进制实数。
我们在第2章中介绍过,用数字的位置表示数值,位值是由基数决定的。在十进制中,小数点左侧的位值有1、10、100,依此类推。它们都是基数的幂,从小数点开始向左,每一位升高一次幂。小数点右侧的位值也是这样得到的,只不过幂是负数。所以,小数点右侧的位置是十分位(10- 1或十分之一)、百分位(10- 2或百分之一),依此类推。
二进制采用的是同样的规则,只是基数为2。由于处理的不是十进制数,所以使用radix point来命名小数点,任何记数系统都可以使用这个术语。在二进制中,小数点右侧的位置是二分位(2- 1或二分之一)、四分位(2- 2或四分之一),依此类推。
那么如何在计算机中表示一个实值呢?我们把实数存储为一个整数加指示小数点位置的信息。也就是说,任何实值都可以由三个属性描述,即符号(正号或负号)、尾数和指数,尾数由该数值中的数字构成,假定小数点在其右边,而指数确定了小数点相对于尾数的位移。十进制的实值可以用下列公式定义:
符号×尾数×10exp
这种表示法称为浮点表示法,因为数字的个数是固定的,但是小数点却是浮动的。在用浮点形式表示的数值中,正指数将把小数点向右移,负指数将把小数点向左移。
**小数点(radix point):在记数系统中,把一个实数分割成整数部分和小数部分的点。
浮点表示法(f?loating point):标明了符号、尾数和指数的实数表示法。**
让我们来看看如何把实数常用的十进制表示法转换成浮点表示法。例如,考虑实数148.69,符号是正号,小数点右边有两位数字,因此,指数是- 2,浮点表示法即14?869×10 - 2。表3-1给出了其他例子。为了便于讨论,假设只能表示五位数字。
表3-1 十进制表示法和浮点表示法表示的(五位数字)值
实 值 浮点值 实 值 浮点值
12001.00 12001100 - 123.10 - 12?31010- 2
- 120.01 - 1200110- 2 155?555?000.00 15?555103
0.12000 12000*10- 5
如何把浮点数转换回十进制表示法呢?基数上面的指数说明了小数点要移动多少位。如果指数是负数,小数点要向左移;如果指数是正数,小数点要向右移。对表3-1中的浮点数应用这个规则。
注意表3-1中的最后一个例子,它丢失了信息。因为我们只保存五位数字来表示有效数字(尾数),所以这个值的整数部分在浮点表示法中没有被精确地表示出来。
同样,下面的公式定义了一个二进制浮点值:
符号×尾数×2exp
注意,只有基数改变了。当然,尾数只能包含二进制数字。要在计算机上存储二进制的浮点数,可以保存定义它的三个值。例如,根据一条通用准则,如果用64位存储一个浮点值,那么其中1位存储符号,11位存储指数,52位存储尾数。当一个值用于计算或显示时,都会采用这种格式。
如果一个数不完整,那么如何才能得到尾数的正确值呢?在第2章中,我们讨论过如何把自然数从一种记数系统转换到另一种记数系统。这里,我们用十进制的例子说明了在计算机中如何表示实数。我们知道,计算机中的所有数值都是用二进制表示的。那么如何把十进制数的小数部分转换成二进制的呢?
把一个整数从十进制转换成其他数制,需要用新基数除这个数,余数是结果左边的下一位数字,商是新的被除数,整个过程直到商为0终止。转换小数部分的操作是类似的,只不过不是用新基数除这个数,而是用新基数乘它。乘法的进位将成为答案右边的下一位数字,乘法结果中的小数部分将成为新的被乘数,整个过程直到乘法结果中的小数部分为0截止。让我们把0.75转换成二进制的。
0.75 * 2 = 1.50
0.50 * 2 = 1.00
因此,十进制中的0.75是二进制中的0.11。让我们再做一个转换。
?0.435 * 2 = 0.870
?0.870 * 2 = 1.740
?0.740 * 2 = 1.480
?0.480 * 2 = 0.960
?0.960 * 2 = 1.920
?0.920 * 2 = 1.840
?…
因此,十进制中的0.435是二进制中的011011…。小数部分会变成0吗?继续乘下去,看看结果如何。
下面让我们看一个完整的转换过程:把十进制的20.25转换成二进制的。首先,转
换20。
20在二进制中等价于10100。现在我们来转换小数部分:
0.25 * 2 = 0.50
0.50 * 2 = 1.00
因此,十进制的20.25在二进制中是10100.01。
科学记数法可能是你已经熟悉的术语,所以我们在这里只简要介绍一下。科学记数法是浮点表示法的一种形式,其中,小数点总在最左边数字的右侧。也就是说,整数部分只有一位。在许多程序设计语言中,如果在输出一个大的实数值时没有指定输出格式,那么这个值将以科学记数法输出。因为早期的机器不能输出指数,所以用字母“E”代替。例如,在科学记数法中,12001.32708将被写为1.200132708E + 4。
科学记数法(scientif?ic notation):另一种浮点表示法。