大概从接触到计算机开始算起,就知道计算机中存储都是以补码形式,且认为补码就是“符号位不变,取反加一”,貌似它像一个已有的实体一样, 已经不需再形式的去定义它, 如同几何学中对“点”和“线”的概念不加定义一样。但事实上,它背后还是有点故事的,在教材中也有提及,但是几乎都被我们忽略了。

why two’s complement?

如果你还是第一次听说这个two's complement的,不妨接下来看看。笔者最近在看自动机方面的书籍,涉及到状态机的时候,经常会拿补码举例子,有时看不懂了,就去google。我就寻思这个补码的英文是什么,结果一搜,two's complement, 直接意译过来,就是2的补码

WTF?当年看计算机组成原理好像没听过这玩意儿啊~

如果世界上没有负数的话,应该轮不到补码出场,至于符号位,那更是没有。但是这不可能。为什么要有补码?而且为什么补码表示才是好的形式?

先不管,反正我们脑海里已经有根深蒂固的概念了:

补码:正数的补码与其原码相同;负数的补码是在其原码的基础上, 符号位不变, 其余逐位取反, 最后+1。符号位为最高位,如果为1就代表负数

举个例子(byte数据,八位),根据上面的姿势你可计算出:

25的补码:

00011001

-25的补码:

11100111

很正确的姿势,下面我们再用另一种姿势计算一遍,同样还是25和-25,但是这次我们不管符号位了

先来看25,把它加上256,用八位表示看看。

25+256=281

你发现这个数用八位都表示不了,在计算机里也就是溢出:

100011001

所以它被截断了,变成00011001,表示的同样还是25。没毛病吧?

再来看-25,也把它加上256,用八位表示看看。

-25+256=231

这个数用八位还是能表示的:

11100111

。。。这玩意儿不就是-25的补码么。 没毛病吧?

为什么是256?因为这个256是作为八位数的最小溢出数。难道不是吗?这个东西很神奇,只要加上这玩意儿,我就能用同一种规则来表示正数、负数和它们在计算机中的运算。

不信?那就来算一下,我知道你们想算减法,减法也就是加一个负数:

125-25,显而易见,答案就是100

用上边的姿势算一遍:

(125+256)+(-25+256)=612

用二进制表示一下:

1001100100

溢出了两位,截断,变成01100100,也就是100

再来算一个 25-125:

(25+256)+(-125+256)=412

二进制表示为:

110011100

溢出了一位,截断,变成10011100,你仔细一看,按照补码定义,这个数就是八位数的-100

继续,再来一个-25-125:

(-25+256)+(-125+256)=362

二进制表示为:

101101010

依然溢出了一位,截断,变成01101010,答案106

老哥,不对啊,答案是-150。

没错,数学上是-150,但你可能忘了它是一个八位数,-150已经超出了八位数在计算机中所能表示的范围[-128,127]。你可以用Java代码敲一下,结果用byte类型表示,-25-125它得出来的就是106,还是没毛病的。

玄学?实际上,这里引出了同余的概念https://www.khanacademy.org/computing/computer-science/cryptography/modarithmetic/a/congruence-modulo

即两整数AB用同一个正整数M(M称为模)去除而所得余数相等,则称ABM同余,记作:

A=B(mod M)

不管是-150还是106,在模256下是同余的(关于取模运算,这个也有点意思,各个语言实现的还不一样,你可以用jsC语言求一个-5%8,你看得到的结果一样不一样)。

“符号位参与运算?”不好意思,我现在不想知道这句话。在我看来,有了上面的过程,才有这个所谓的“符号位参与运算”。或者说,符号位是因为同余才变得有意义。再进一步,补码就是同余,具有同余关系的两个数为互补关系,其中一个称为另一个的补码。。像上面的例子,8位下最多能表示256个数,即0255,可我还想表示一些负数该怎么办?根据同余概念:-1 ≅ 255-2 ≅ 254-3 ≅ 253,等等,然后我再砍掉一半正数,拿它们表示负数,这不就是补码么?所以补码厉害就在,它可以把带符号数和无符号数的加减运算全部都当作无符号数来进行运算,还能自动解决溢出的问题(反码解决了符号问题,但是溢出后要加1,还有-0+0的问题),简化了计算机中运算器的线路设计,增加了处理器效率,减少了设计制造成本。而这一步转化,已经由编译器完成了,它将你的负数表示成了补码形式,使得计算机能够明白你的意思。

最后,如果看懂了的话,再抛个问题,补码10000000为什么表示的是-128,而不是-0?真的是规定的吗?