跳转至

补码再思考

2024.2.18

  我曾在很多课程中学习过补码的知识。然而,大多数时候我受到的教育都是机械地接受: 正数和0的补码就是该数字本身再补上最高比特0。负数的补码则是将其绝对值按位取反再加1。补码可以避免+0和-0的歧义。但是为什么要这么定义,以及它是如何避免+0和-0的,很多地方都没有解释。本文通过另一种看待补码的角度,尝试解释上述问题。

  首先,我们回顾原码的定义:

**原码是指一个二进制数左边加上符号位后所得到的码**

  由于原码是强行添加的符号位,因此符号位本身并没有权重。然而,数学知识告诉我们,在一个正常的进制中,每个位上都应该有某个权重。因此,原码在数学上是不完备的,势必会产生一些问题。而这个问题在这里就体现为+0和-0。

  那么事情就变得很清晰了:我们需要给符号位也赋予一个权重。

  我们采用如下定义:将第N位的权重定义为\(-2^{(N-1)}\),第i (i<N) 位的权重定义为\(-2^{(i-1)}\)

  好了,这样我们的符号位也有了权重。但是,为什么是这个权重?我们可以来看看这样定义会发生什么。

  我们以一个四位二进制数1101为例。当它作为无符号普通二进制数时,1101=13。当它作为补码时,1101=-3。现在我断言,它们在进行运算的时候,作用是相同的。为什么呢?这是因为四位二进制补码的表示范围是\([-2^{(4-1)},2^{(4-1)-1}]=[-8,7]\),这是一个长度为16的区间,也就是说,由于溢出的原因,所有得到的结果都要模16到[-8,7]这个区间中,所以-3就等于+13。这样,补码就巧妙地将带符号的二进制数中的负数转化为了正数,从而可以正常的进行二进制计算,而且更加符合加法器的原理。

  现在我们来看看0的问题。让我们来看看1101加几等于0。由于模了16,所以16=0。所以1101+0011=10000=0000。而这个0011,也就是1101的相反数。这就是本文最开始的经验性定义的由来——因为我们要凑10000,所以把正数取反相加,就是1111,再加1,就可以完美产生10000这个数。因此,一个负数的补码,就是它对应的正数取反加1。

  看到这里,你应该知道了补码的本质优势————可以通过模运算的性质,简单地将加法运算扩展到负数,而不需要特殊的硬件电路或逻辑。

  最后,我还很好奇,为什么反码的英文是“1's complement”,补码的英文是“2's complement”。关于这方面的中文资料很少。知乎用户 @前端西瓜哥 在他的知乎文章补码到底是什么?中 认为:

补码原意是 “2的补数”。这个命名虽然没有描述补码的定义,但它描述了补码的一个特性:一个补码可以通过被\(2^w\)减去,得到它的相反数,即\(-x=2^w-x\)

  我认为这种说法有待商榷,因为以此类推,因为反码是1's complement,所以一个反码可以通过被1减去,得到它的相反数。而这显然与反码的定义相冲突。

  还有一种说法是ChatGPT说的,它说反码只做了一个操作,涉及到1个值,所以是1's complement。补码因为还要+1,涉及到2个值,所以是2's complement。我认为这种说法也很牵强。总之我没有在中文互联网查到让我满意的答案。欢迎读者查阅相关英文文献并和我交流!