很久很久以前,当我参加 NOIP(信息学奥林匹克竞赛)的时候,老师是这么讲补码的:
int 型数据的最高位是符号位,符号位为 0 就代表这是正数,为 1 就是负数。但是,+0 和 -0 理应是同一个数,但在二进制中却表示成了两个不同的数(以 8 位为例,二进制表示): 00000000 和 10000000。所以,我们引入了补码,补码就是,对负数而言,符号位不变,其他位取反,然后再加 1。于是, -0 的补码就是 10000000 -> 11111111 -> 00000000 与 +0 一致了。
其实当时我就该意识到,仅仅为了 0 这一个数便创建了“补码”这个概念,未免太浪费了。我最近看了 CS: APP 这本书,才进一步理解了补码。
补码,就是为有符号整数而创建的概念。对于无符号整数,是不存在“补码”的概念的。我们先看一个无符号数 00100101,它是怎么转换成十进制的?加权求和,0×2^7+0×2^6+1×2^5+0×2^4+0×2^3+1×2^2+0×2^1+1×2^0=37;无符号数 10000011,1×2^7+0×2^6+0×2^5+0×2^4+0×2^3+0×2^2+1×2^1+1×2^0=131。
对于有符号数呢?假如我们不用补码,那么有符号数 10000011,就是 -(0×2^6+0×2^5+0×2^4+0×2^3+0×2^2+1×2^1+1×2^0)=-3。人这么算起来是挺舒服,但是计算机会很难受,凭什么我最高位的权值没了?只能做符号?
然后我们试试补码。10000011 的补码是 11111101,如何用补码求它的十进制呢?其实这跟无符号数是一样的,只不过,最高位的权值,不是 2^7,而是 -2^7。求值:1×(-2^7)+1×2^6+1×2^5+1×2^4+1×2^3+1×2^2+0×2^1+1×2^0=-3,是不是很神奇?
对于正数而言,就更简单了,与无符号数一致。
计算机只能做加法(不是),所以,我们看看用补码做加法,有什么好处。
先计算 -13+10 吧,很简单的二进制竖式加法:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 |
0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 |
11111101 即为 -3 的补码。
再算一个,-8+9:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
神奇吗?最高位的 0 不见了,变成正数 1 了。
补码,就是计算机内部有符号数的存储方式,我们不要认为补码是由原码“转换”而来的,补码本来就代表了实际的数字,11111101 == -3,就这样。