Observation
我们知道补码是为了二进制数的符号位直接参与加减运算而设计出来的,其原理可以用下面这个例子来展示:
∵
−
1
m
o
d
4
=
=
3
m
o
d
4
∴
(
3
−
1
)
m
o
d
4
=
2
(
3
+
3
)
m
o
d
4
=
2
\quad∵-1 mod 4\ ==\ 3 mod4 \\ ∴(3-1) mod 4\ =\ 2 \\ \quad(3+3) mod 4\ =\ 2
∵−1mod4 == 3mod4∴(3−1)mod4 = 2(3+3)mod4 = 2
可以看到,因为 -1 和 3 对 4 取余的结果相同,所以 x-1 对 4 取余和 x+3 对 4 取余的结果相同。这也是我们想要计算机拥有的能力——即将减法用加法代替,这也是补码和余数最直接的关系,补码即在二进制上实现了这种同余的性质。
基于上述,计算机为了省去判断运算是加还是减,所以利用补码来实现将加减运算都用加运算实现:下面是一个例子,利用补码求解 2 − 1 = 1 2-1=1 2−1=1 这个式子。
-
我们知道 2、-1 的机器数为 0010 和 1001,补码为 0010 和 1111。
-
补码运算为
0010 + 1111 = 0001 ( 其 实 应 该 是 10001 , 但 是 因 为 我 们 用 4 位 来 表 示 , 所 以 溢 出 部 分 就 抛 弃 ) 0010 + 1111 = 0001 \quad(其实应该是 10001,但是因为我们用 4 位来表示,所以溢出部分就抛弃) 0010+1111=0001(其实应该是10001,但是因为我们用4位来表示,所以溢出部分就抛弃) -
求 0001 的补码为 0001 即得到最后的结果为 1。
解释
那么大牛们这样设计补码的原理是什么呢?
我们知道,补码不是单纯的同余数(否则 -1 的补码应该是 3——0011),这是因为我们希望可以让符号位参与运算。
如何让符号位参与运算呢?既然能让符号位参与运算,那么就是意味着在运算过程中,符号位会被当成普通的数值位一样,那么补码设计的思路也就呼之欲出了:
仍用上面的例子,我们重新分析一下 -1(1001)的补码(1111),按照上述提到的,我们希望补码将符号位当成数值位一般,这样就可以直接运算了。那我们看,如果把 -1 的补码 1111 看成一个数,那么其代表的应该是 15,不难发现 -1 ≡ 15 (mod 16)。
结论
这下我们就清楚了,因为被取余的数其实是可以任意的,而我们设计补码主要就是为了给负数找一个对应的正数来将减法转化为加法,所以设计方法为,补码为负数针对“最大数”的同余数。
例如: -1 用 1001 表示原码,那么我们就是认定用 4 位二进制数表示一个真值,所以其最大数即为 2 4 = 16 2^4 = 16 24=16,那么因为 − 1 ≡ 15 ( m o d 16 ) -1 ≡ 15 (mod 16) −1≡15(mod16),所以其补码对应为 15 的无符号数 1111(毕竟补码都是针对负数来说的,负数的补码一定都是正数)。
同理,若用一个 int(32位表示一个数),那么最大数即为 2 32 2^{32} 232,那么因为 − 1 ≡ 2 32 − 1 ( m o d 2 32 ) -1 ≡ 2^{32}-1\ (mod 2^{32}) −1≡232−1 (mod232) ,所以其补码为 111 , ⋯ , 111 111,\cdots,111 111,⋯,111 (32 个 1) 。