整数在计算机中是通过二进制的比特串来存储的。
将整数编码成二进制比特表示有几种方法,即通常所说的原码,反码和补码。
比如我们用4位模式xxxx,其中x
∈{0,1}
来存储一个整数,那么第一位是被预留表示整数的符号的,0代表正数,而1代表负数。比如-3就是1011;2就是0010
除了这个第一位的表示,剩余的位就是正常的二进制翻译,这种表示称之为原码。
原码的一个缺点在于计算的时候会出现问题,特别是整数的减法。最简单的比如 0−1 ,如果用原码表示,直觉上似乎是0000-0001=1111,这样转化成原码就是0-1=-7了,显然不正确;或者我们不知道应该怎么算。再比如1+(-1)=0001+1001=1010=-2?所以,原码的计算是有问题的。
事实上,在计算机之中,我们不需要考虑减法,而只考虑加法,当两个数相减的时候,我们考虑负数。另一方面,这样做需要配合上补码的表示,便可以形成完美的无差错运算体系。那么,究竟什么是补码?
一般地,对于一个整数z,它的补码有两种人工的求法,当然他们的结果是一样的。如果它是正数,则其补码就等于它的原码;如果它是负数,那么它的补码是这样定义的,将其绝对值对应的原码(即-x的原码)的位模式从最右数起,碰见的第一个1不动,而将这个1左边的位模式全部取反(即0变成1,1变成0)。如图1.
另一种方法,就是通过反码。简单来说,所谓反码就是按位取反。对于正整数,反码和原码一样,而对于负数,它的反码等于将原码除符号位以外的位全部取反。这样,第二种求补码的方式就是,正整数和原码一样,而负整数而言,是将它的反码+1(即通常的二进制加1)。
最自然的问题,就是为什么这样定义?这样定义为什么合理?并且使之恰好成为完美的运算体系?
首先我们注意到,一套位模式(简单起见我们考虑短的位模式作分析,长度不是本质的问题,对于长的模式也是一样的道理),比如说4位xxxx,那么它所能表示的整数有一个范围。当第一位由于计算机的原因必须用来表示符号以外,这个位模式能表示的最大正数显然是0111,也就是7. 那负数最小应该是1111,也就是-7(如图2). 这样的表示方式有一个天然的缺陷,即1000没法表示任何东西,因为-0显然没有必要。我们先不管这个问题,先看一个更重要的问题,就是在计算机之中正常的加法也“不正常”,也就是所谓的溢出。当我们仅仅能用4位模式(或者一般地,计算机中就是有限位模式,不论多大都是有限)来存储数据的时候,我们的存储范围是有限的,即我们不可能存储所有的整数。因此加法的封闭性是无法保证的。(而也是没有必要保证的,因为我们采用的是正常的整数加法,溢出了就是工程上需要考虑的问题。另外,如果要保证加法的封闭性,我们对于这个有限集合上的加法就需要重新定义了,即通过“模加法”,它其实也可以通过同态来得到。但是这样就不是正常的加法运算了,而我们在求两个整数加法的时候是要常规的加。)
这个时候,我们需要确定这个有限集中的每个元素对应的位模式表示。我们要做的,是要使得位模式表示既能够一一对应这个范围内的整数,又保持运算。从代数上说,这差一点就是就是同态。之所以说差一点,是因为溢出的存在,使得这两个集合的加法运算都不是封闭的。
于是我们考虑更一般的情形,所有的整数集合
Z
和它的二进制表达
B
之间是一一对应的(记为
f
),或者更进一步说,他们是加法同构的(一定要注意,这个时候,对于一个负整数-y,它被这个同构对应到一个-xx..x,后面的xx…x是y的二进制表示)。当我们采用计算机的存储模式,即有限位位模式的二进制存储(首位作为符号位),不妨说是n位比特串。记
这个同态就是补码的编码方式。事实上,
f
是直接的,正数就是二进制表示,负数就是绝对值的二进制表示前面加个负号。然后,对于
我们说明两点事情。第一,
Bm
就是计算机当
n
位存储模式下的计算方式,它是封闭的,并且它是一个加法群。第二,我们的目标是考虑
最后,我们从上面的复合同态的定义方式可以知道补码的人工计算方式。我们需要强调,我们应该这样来看问题,即补码不是从原码来的,补码是一种映射或者说编码方式,它是一种有自己理论依据的完善的计算机处理整数的编码方式,是一个独立的体系。而原码是更接近人类理解的计算机的另一种整数存储方式。所以我们所谓的从原码求补码,是试图找这两个编码之间的关系(图4)。
我们最后说明为什么从原码求补码的方式是这样的,或者说我们解释为什么补码是如我们一开始定义的那样的。这其实很简单,我们只需要分析这个复合同态,结合 B 到原码的一一映射就可以知道。事实上,对于正数(我们指原码),我们看到,它的原码和补码是一样的;而对于负数1xx…x(除了100…0以外,这个数是补码仅有的,原码不会有这个数),我们看到,我们先求它的绝对值,即将符号位变为0,得到0xx…x. 然后再求这个绝对值映射到 Bm 之后(也就是样子完全没变)在 Bm 之中的逆元。而逆元就是和它相加等于00…0的位模式。要和0xx…x相加等于00…0,那显然就是考虑0xx…x从右数起的第一个1不动,其余左边的全部取反;或者说,这个逆元就是1xx…x最右边的1和符号位的1不动,两个1之间的数全部取反得到的。这就是这个原码的补码表示。
参考文献:
J.Brookshear,《计算机科学概论(第11版)》,人民邮电出版社。