补码是如何把减法转化为加法的?

我们都知道在计算机中,数字是以补码的形式存储的,好处就是利于减法计算,可是为什么呢?让我们来试图回答以下几个问题。

  1. 什么是补码,如何计算?
  2. 补码如何将减法转化为加法?

但凡编程入过门,都知道补码是怎么回事。正数的补码是其自身,负数的补码是其绝对值取反加一。

在这里我们不考虑正数,因为正数没有补码,其实是不需要更准确。我们只看负数的补码,这里有两个信息:取反和加一。由这两个信息引出两个问题:

  1. 取反是什么意思?
  2. 为什么要加1?

二进制不符合人的直观认知,所以我们在十进制下来看看补码的计算。以15-7为例,用补码计算。

首先我们假设数字位宽是4位,最高位为符号位,15表示为0015,-7应该是1007。但我们清楚的知道这样计算出来的结果0015+1007=1022是不对的。我们要将-7用补码来表示,这里涉及第一个问题,如何取反?

在二进制中取反就是0变1,1变0,那么在十进制中取反的含义是什么呢?

取反的本质是该进制下的极数减去原数。所谓极数就是该进制所能表达的最大数,比如4位十进制所能表达的最大数是9999,那么-7取反就是9999-7=9992。所以,为什么二进制取反是0/1互换呢,因为二进制所能表达的极数是1111,相减的结果当然就是0变1,1变0了。

知道了如何取反,那么-7的补码就是9992+1=9993,现在在来计算15-7,就变成了0015+9993=10008,但是由于我们只有4位位宽,所以最高位的1会被截断,最终结果变成0008

看到这里,是不是就觉得补码计算没那么神奇了。我们将计算过程进一步展开:

15 − 7 = 0015 − 0007 = 0015 + 9999 − 0007 + 1 = 0015 − 0007 + 9999 + 1 = 0015 − 0007 + 10000 = 0015 − 0007 \begin{aligned} 15-7&=0015-0007\\&=0015+9999-0007+1\\&=0015-0007+9999+1\\&=0015-0007+10000\\&=0015-0007 \end{aligned} 157=00150007=0015+99990007+1=00150007+9999+1=00150007+10000=00150007

从上面的过程中我们可以清楚的看到减法是如何变成加法的,以及补码取反之后为什么要加1。现在我们再来审视前面提出的问题,补码把减法变成加法的原理是位宽溢出。我们之所以可以做直接做加法,是因为补码已经帮我们做了一次减法,就是求反码的时候。只不过在二进制中,求补码的减法刚好就是取反!所以,取反的意思其实是做减法

再次回到文章标题,其实补码并没有将减法转化为加法,而是补码帮你做了减法。

最后我们再来看看为什么正数没有补码,因为补码中蕴含的是减法运算,而正数代表的是加法运算,负数代表的才是减法运算。