今天给学生上数字逻辑第一节课,主要讲了数制,后面简单提及了原码、反码和补码,碰到了两个问题:第一,十进制数转八进制数,学生练习时卡壳,不知道无从下手;第二,原本以为原码、反码、补码应该是一年级甚至中学时就应该解决的问题,实际上原来根本不是这么回事。中学老师即使讲过,估计也是对付考试的方式简单提及,并未从本质上进行讲解(那时候他们忙着对付高考,哪有闲工夫讲这些题外的知识)。
真值(原码)
首先,我们明白计算机中的数都是用二进制表示的,如3用二进制表示为:
(3)10 = (11)2
那如果-3呢?
显然,最直接的办法是用0或1来表示符号。很自然的,我们可以在数值前加符号位来表示,用0表示正数,用1表示负数。为了描述方便,我们后面统一用8位字长来举例描述,如:
(3)10 = (00000011)2
(-3)10 = (10000011)2
对于8位二进制数,原码的表示范围是:[(11111111)2, (01111111)2],即[-127, 127].
从直觉上,貌似已经解决了正负数的表示问题。但是,问题来了,第一个问题是:0的表示不唯一(计算机的准确性和唯一性遭遇挑战):
0 = (+0)10 = (00000000)2
=(-0)10 = (10000000)2
随之而来的是第二个问题,如3-5如何处理?如果用二进制的减法规则
(00000011)2 - (00000101)2 = (11111110)2 (原码中(11111110)2 = (-126)10)
如果按照3-5=3+(-5),则
(00000011)2 - (10000101)2 = (10001000) 2 = (-8)10
也就是说,不论按照原码的减法和原码的加法计算,我们不仅没有得到正确的结果,而且两种方式得到的结果还不一致。在计算机中,为了降低电路设计的复杂性,所有的减法都是按照加法来计算的,但是从上面的结果看,这些结果无疑都是错误的;并且0有两种表示方法,这就给计算中带来了困难,在计算时到底是用+0还是用-0?计算过程中的本质问题是:符号位参与计算,不可避免地会导致计算错误,即原码中的符号位是无法参与计算的。
正确的做法应该是:如果用原码计算减法操作,我们需要先判断谁的绝对值大,如果被减数的绝对值大,则计算结果的符号位为0,反之则为1,结果中的数值应该是用绝对值大得减去绝对值小的即为正确结果。如3-5的计算过程应该是:由于3<5,所以结果为负,符号位为1,5-3=2,所以结果为-2。如果是这样的计算过程,计算机应该设计绝对值比较电路,并且设计减法电路。很显然,这会极大地增加电路设计的成本,并且增加运行中的功耗。
反码
为了应对原码计算中的问题,有人发现反码可以解决一些问题。首先,我们看反码的定义:
正数的反码与原码一样,负数的反码为:符号位取1,数值部分按位取反。
如-5可表示为(11111010)2。 这样,上例中的计算似乎可以顺利解决了
3 - 5 = 3 + (-5) = (00000011)2 + (11111010)2 = (11111101) 2 = (-2)10,结果正确,ok了吗?
那我们再看下面一个例子:-3 - 5 = -3 + (-5)
-3 + (-5) = (11111100)2 + (11111010)2 = (11110110)2 = -9
其结果显然是错误的,即当符号位存在进位时,其结果不正确。那结果是否可以修正呢?还是看-3 + (-5),我们看其演化过程,如果符号位的进位不丢掉,则结果为:
-3 + (-5) = (11111100)2 - (11111010)2 = (111110110)2
如果此时,我们将符号位进位加到最后一位,这结果变为(11110111)2=-8。也就是说,补码运算允许符号位参与运算,但是,当符号位存在进位时,我们需要对符号位进行修正,将进位加到计算结果的最后一位,修正得到正确结果。但是,这样同样需要设计判断符号位是否存在的电路?
另外,在反码中,也没有解决0=+0=-0的二义性问题。
补码
为此,又有人根据模运行的规律,提出了补码的规则。首先,我们看补码的定义是什么?
正数的补码与原码相同,负数的补码为:符号位取1,数值部分为真值取反,然后再加1,即[Y]补=2n+Y,这里Y是整数, 2n为模M
如-3=(11111101)2, 0=-0=(11111111+1)2=(00000000)2 = +0,即对于0的问题,补码将+0和-0统一起来了,解决了关于0的二义性问题。
接着我们看3-5和-3-5两个计算的问题
3-5=3+(-5)= (00000011)2 + (11111011)2 = (11111110)2 = -2
-3-5=(11111101)2 + (11111011)2 = (11111000)2 = -8
即补码中的符号位参与运算,完美地解决了原码中和补码中存在的问题。那为什么补码能正确解决这些问题呢?下面我们给出一般的证明。
为什么补码能正确进行计算
要证明补码为什么能正确进行加法运算,其实需要证明:[X+Y]补 = [X]补 + [Y]补
证明:
1)若X > 0, Y > 0, 这X+Y > 0,且X=[X]补, Y = [Y]补,所以[X+Y]补 = [X]补 + [Y]补;
2)若X > 0, Y < 0, 则X=[X]补, [Y]补=M+Y(mod M),所以[X]补 + [Y]补=M+X+Y,这里需要讨论两种情况:a)若X+Y>=0, 则M可舍掉, [X]补 + [Y]补 = M+X+Y = [X+Y]补;b)若X+Y < 0,由补码定义, [X]补 + [Y]补 = M+X+Y = [X+Y]补
3)若X < 0, Y > 0, 同2)
4)若X < 0, Y < 0, 则X+Y < 0. [X]补 = M + X (mod M), [Y]补 = M + Y (mod M), [X]补+[Y]补= M + X + M + Y = M + (M + X + Y) = M + [X+Y]补 = [X + Y]补(mod M)
后面这段证明,感兴趣的童鞋要好好体会,这个才是正确理解补码运算的关键。