补码是什么?记得C语言课本上写的,负数补码就是反码+1。很简单的一句话,但是讲的不明不白的。

  1. 为什么负数补码是反码+1
  2. 为什么正数的补码是其本身?
  3. 为什么127下一个数是-128?

下面拿一个字节来扯淡。

正数的编码是其本身

在计算机里,全部都是用补码表示数,补码就是一种对数的编码规则,一个二进制数就是一个code point,类似于unicode是一种字符的编码规则,一个code point可以对应一个真值。包括原码和反码,都是一种编码。所以这可以解答第二个问题,因为在补码里,我们把二进制0000 0000规定为0,最高位0的编码为正数,所以正数的补码是其本身,更确切的说法,在补码中,正数的编码是其本身

所以8bit的范围是[-128,127],当然我们可以规定8bit的范围是[-129,126],或者[-127,128]这样做的后果就是:正数的编码不是其本身。

这会导致什么呢,我觉得这不会导致什么,无非就是对二进制认知会出现点问题,比如代码里写byte a = 1;,那么我们期望得到00000001,但是如果编码不是[-128,127],那么我们声明的byte就不是我们期望的二进制序列——我们得按编码规则然后推出00000001对应的真值是多少,然后才能正确赋值。但是从计算的角度来说,只要编码确定了,计算就不会出错。

原码和反码

有很多谈补码的,有一个观点是补码是在反码的基础上设计出来的,再加上这句负数补码是反码+1,就似乎更是如此了。

但是这有点由果推因的味道。反码英文是1's complement,称“1补数”,补码是2's complement,称“2补数”,其中complement的意思就是补码(后续补码单指2's complement),所以这两种码应该是不能看作依赖关系的,只能说两者之间可以互相推导。

从原码开始说起,原码是这么编码的:最高位是符号位,0是正数,1是负数,其他表示绝对值。但是对计算机来说,还得判断最高位才能计算,不行🙅。

反码是最高位作为符号位,负数保留符号位1不变,剩下位按位取反。反码只是为了让符号位参与计算,计算结果和真值计算结果相比,时对时错,其中根本问题是有两个0(+0/-0),这需要通过循环进位的规则才能正确应用。

要正确应用反码,运算规则是,如果最高位产生进位,要把进位循环进位到最低位。比如0100 1000 + 1100 1000进行计算,结果是1 0001 0000,那么在模=2^8的情况下最终结果应该是0001 0001。我的理解是,最高位进位,说明越过了(+0/-0),是少一个数的,需要+1来调整。

Internet协议IPv4,ICMP,UDP以及TCP都使用同样的16位反码检验和算法。虽然大多数计算机缺少“循环进位”硬件,但是这种额外的复杂性是可以接受的,因为“对于所有位(bit)位置上的错误都是同样敏感的”。 在UDP中,全0表示省略了可选的检验和特性。另外一种表示:FFFF,指示了0的检验和。 (在IPv4中,TCP和ICMP都强制性地规定了检验和,而在IPv6中可以省略)。

补码

补码系统利用了模的思想,模就是用来表示一个计数范围,比如⏰的计量范围是0~11,模=12。二进制0000 00001111 1111的模就是1 0000 0000

上面说到模,补码系统就是这么规定:一个数和他的二补码之和等于模。在8位数中,1 0000 0000和0的值是等价的,所以一个数和他的二补码之和等于0,那么,一个负数在补码系统里可以用正数的二补码来表示。同时可以得出一个结论,求一个数的相反数,只要求他的二补码就好了。

由于n的二补码=(模 - n)=(1 0000 0000 - n)=1 + 1111 1111 - n1111 1111 - n刚好又是按位取反,是反码,所以一个数的二补码是反码+1。但这只能作为一个定理,不能作为定义。-128不适用“负数补码是反码+1”,但是,在补码系统里,这个数却是存在的。事实上,-128的二补码等于其本身。

编码是有序的

在我看来,二进制没有正负之分,这个正负是人为加上去的,机器并不需要知道当前的数的正负。他只要拿到0000 10001111 1101相加,得到0000 0101,那么这个0000 0101是表示什么,机器不感兴趣。所以说即使8bit的范围变成[-129,126]或者[-127,128],也不影响结果,只要编码是有序的,二进制计算法则总能保证结果是正确的(当然,这就不是补码了)。

这里的有序,指的是二进制的顺序和真值的顺序是一致的。比如不可以规定{01111 1111,0111 1110,...,1000 0001,1000 0000}对应[-128,127],这样二进制顺序和真值顺序不一致。

像原码,-15 -> -14,其编码1000 1111->1000 1110,这是不按顺序的。反码大致保证了顺序,但是存在两个0的code point,需要引入额外的调整才能消处两个0的问题。

真值的顺序和二进制顺序一致,所以127下一个数是-128,是因为0111 1111加1得到1000 0000

实际上,CPU的计算是不区分正负的,但是逻辑部分是区分正负的(大小端也是如此)

相反数

  1. 不用乘法除法减法,怎么求相反数?按位取反+1,即求他的二补码。
  2. 8位数里,1000 0000的相反数?是1000 0000本身。

后记

我的理解,在补码系统里,二补码是成对存在的,因此上面有时用补码有时用二补码,前者想表达一个数在补码系统里的表示,后者表示一个数对应的另一个数。

参考

  1. 趣谈计算机补码
  2. 为什么8bit限制是-128到127而不是-127到128?
  3. Two’s complement