前言
补码(Two’s complement) 是一种用二进制表示有符号数的方法,也是一种将数字的正负号变号的方式,常在计算机科学中使用。
采用补码存储的最大优点是只用一种运算方法就能计算正数和负数,不用考虑操作数的正负号。在做减法时可转换成加上被减数的相反数的补码,这样计算单元内只要有加法电路和补码电路就能完成各种整数的加减运算,极大简化了电路上的设计。另外使用补码系统后就不会出现 +0 和 -0 两种 0 的表示方式,减少了数据运算的复杂度。
正文
为什么使用补码可以使加法计算出减法的效果呢,这是巧妙运用了存储单元的溢出机制。举一个同样存在“溢出”的例子:时钟。
在时钟里,时针的最大刻度是 12,超过这个值(称为模)将回到起点。假设现在要把时针从 4 点钟调到 1 点钟,可以逆时针减 3 小时,也可以顺时针加 9 小时,效果是完全一样的。于是在时针系统里存在:4-3 = 4+9,在模为 12 的时针系统里 3 和 9 就是一对补数,利用补数形式可将式子写成:4-3 = 4+(12-3)。再将时针回到原点看成是取模运算的话,就能得到: (4-3) mod 12 = [4+(12-3)] mod 12 = 1,等式严格成立。
从上面例子可以初步看出将减法转换成加法的一种办法,就是:减去一个数等于加上这个数的补数再取模。这只适用于有模的情况下,而且计算补数似乎又不可避免的用到减法。但一切就是这么神奇,计算机就是个有模的系统,补码就是可以不用减法得到一个数的补数。
现在使用 8 位整型单元举例,8 位整型单元共能表示 28 = 256 种结果,256 就是它的模,当结果大于等于模时多出的高位就会溢出。
现在假设要计算:64-16 = 48,根据前面的结论,减去 16 可改为加上 16 的补数后再取模,所以式子可转换为:[64+(256-16)] mod 256,结果同为 48。我们逐步分析这个过程在计算机里是如何实现的。
先看 16 的原码:0001,0000。
首先计算 16 的补数:256-16,因为 8 位整型最大只能表示255(二进制:1111,1111),所以需要分成两步计算:255-16+1,二进制为:1111,1111 - 0001,0000 + 1,得:1110,1111 + 1,我们发现 1110,1111 正是 16 的原码按位取反,这很好理解,如下图所示。
于是,在计算机内,根本不需要通过计算 256-16 来得出 16 的补数,只要对 16 进行按位取反后再加 1 就能得到结果,最后结果为 240(二进制:1111,0000)。至此就可以明白为什么负数的补码是其正数原码的按位取反后加 1 了。
得到 16 的补数后就可以下一步计算:64+240,化作二进制:0100,0000 + 1111,0000,结果为 1,0011,0000,共有 9 位,因为存储单元最大只能存储 8 位,多出的高位将溢出,得到 0011,0000,正是我们要计算 64-16 的结果 48,于是高位的溢出就巧妙地成了最后的求模运算,帮我们得到正确的结果。