在计算机科学和数字电路中,"补码" (Complement Code) 是一种表示有符号整数(正数和负数)的方法。它解决了早期表示方法(如原码、反码)在算术运算上的一些复杂性和问题。
我们通常说的“1补码”和“2补码”实际上是指 “反码 (One's Complement)” 和 “补码 (Two's Complement)”
1. 原码 (Sign-Magnitude Representation)
在介绍反码和补码之前,先简单说一下原码。
原码: 用最高位表示符号(0代表正数,1代表负数),其余位表示数值的绝对值。
- 优点: 直观,易于理解。
- 缺点:
- 有两个零:
+0
(0000) 和-0
(1000)(以4位为例)。这导致判断零的逻辑更复杂。 - 加减法复杂: 进行加减运算时,需要先判断符号,再根据符号决定是做加法还是减法,最后确定结果的符号。
- 有两个零:
2. 反码 (One's Complement) - "1补码"
定义:
- 正数: 它的反码就是其原码本身。
- 负数: 它的反码是在其原码的基础上,符号位不变,其他所有位取反(0变1,1变0)。
- 例如,对于 4 位二进制数:
+5
的原码是0101
,反码也是0101
。-5
的原码是1101
。取反后(符号位不变),反码是1010
。
- 例如,对于 4 位二进制数:
特点:
- 有两个零:
+0
(0000) 和-0
(1111)(以4位为例)。 - 加法规则: 可以直接进行加法运算,但如果最高位有进位(即结果溢出到符号位之外),需要将这个进位加到结果的最低位(这被称为“循环进位”或“末位进位”)。
- 减法:
A - B
可以转换为A + (-B的反码)
。
为什么叫 "One's Complement" (反码)?
因为一个数的反码加上它本身,所有位都变成1(假设不考虑符号位)。例如,0101 (+5)
和 1010 (-5的反码)
。0101 + 1010 = 1111
。在数学上,这可以理解为对 2^n - 1
的“补足”,即 X + X' = 2^n - 1
。
3. 补码 (Two's Complement) - "2补码"
定义:
- 正数: 它的补码就是其原码本身。
- 负数: 它的补码是在其反码的基础上,末位加 1。
- 方法一: 负数的原码 -> 符号位不变,其他位取反 -> 结果末位加1。
- 方法二: 从右向左找到第一个
1
,这个1
和它右边的所有位保持不变。这个1
左边的所有位都取反。 - 例如,对于 4 位二进制数:
+5
的原码是0101
,补码也是0101
。-5
的原码是1101
。- 方法一:反码
1010
,末位加1 ->1011
。所以-5
的补码是1011
。 - 方法二:
1101
。从右向左第一个1
在最低位。保持1
,它左边的110
取反变成001
。组合起来得到0011
。注意:这里方法二是对正数求负数补码,或对负数求正数补码时使用的。更常用的是从原码或反码转换。
- 方法一:反码
核心优势:
- 唯一的零: 只有
0000
表示0
。这解决了原码和反码的双零问题。 - 统一的加减法: 所有的加减运算都可以通过加法来完成,无需额外处理符号位。
A - B
等同于A + (-B的补码)
。- 例如,
5 - 3
(4位):+5
的补码是0101
+3
的补码是0011
-3
的补码是:0011
(原码) ->1100
(反码) ->1101
(补码)0101 + 1101 = 10010
。在 4 位系统中,最高位的进位1
被舍弃,结果是0010
,即+2
,正确!
- 表示范围不对称: 对于 n 位二进制数,补码可以表示
-(2^(n-1))
到(2^(n-1) - 1)
的范围。例如,4 位可以表示-8
到+7
。这是因为少了一个0
的表示,所以负数可以多表示一个。
为什么叫 "Two's Complement" (补码)?
这是因为一个负数的补码加上它本身,结果是 2^n
(在模 2^n
意义下为 0
),这在数学上被称为对 2^n
的“补足”。
- 假设有 n 位二进制数,其能表示的最大范围是
2^n
。 - 一个正数
X
的负数补码-X_补码
,实际上是2^n - X
。 - 例如,在 4 位系统中,
n=4
,2^n = 16
。-5
的补码是1011
(二进制的11
)。- 我们知道
16 - 5 = 11
。这个11
就是-5
在 4 位补码中的表示。 - 当进行
X + (-X)
运算时,实际上是X + (2^n - X) = 2^n
。在 n 位系统中,2^n
恰好是1
后面跟n
个0
(例如10000
forn=4
)。这个最高位的1
会被截断,剩下的n
个0
就是0
。这完美地实现了加法运算的统一。
这种“补足”的概念使得处理器在进行加减法时,无论数字是正数还是负数,都只进行简单的二进制加法,最高位溢出就舍弃,从而大大简化了硬件设计。这是现代计算机普遍采用补码表示有符号整数的原因。
理解这些概念的关键在于 模运算 (Modulo Arithmetic)
核心概念:模运算 (Modulo Arithmetic)
在计算机中,我们使用的二进制数是固定位数的。这意味着无论运算结果多大,它都会“环绕”在一个特定的范围内。这就好比一个时钟:12点 + 3小时 = 3点,而不是15点。这里的“12”就是模数。
对于一个 N 位的二进制系统:
- 它能表示的整数范围是
0
到2^N - 1
。 - 所有的运算都在 模
2^N
的意义下进行。 - 这意味着任何超出
2^N - 1
的结果都会“溢出”,最高位的进位会被丢弃。例如,在一个 4 位系统中 (N=4
),模数是2^4 = 16
。10 + 7 = 17
。在 4 位二进制中,17
会表示为1
(溢出)0001
,最终结果是1
。也就是说,17 mod 16 = 1
。
1. 反码 (One's Complement) 的数学原理
-
表示负数: 在N位系统中,一个正数
X
的反码X_反
(表示-X
) 的数学定义是:
X_反 = (2^N - 1) - X
这也就是“所有位取反”操作的数学解释。
例如,对于 4 位系统,2^4 - 1 = 1111_2
(即 15)。
如果X = 0101_2 (+5)
:
-5
的反码就是(1111_2) - (0101_2) = 1010_2
。这确实是0101_2
所有位取反的结果。 -
加法运算: 我们的目标是计算
A - B
。在反码系统中,这被转换为A + (-B)
。
假设B
是一个正数,那么-B
的反码表示是(2^N - 1) - B
。
所以,我们要计算的是A + ((2^N - 1) - B)
。
重新整理一下:A - B + (2^N - 1)
情况一:结果
A - B
为正数或零。
例如:5 - 3 = 2
(使用 4 位系统)
A = 0101_2 (+5)
B = 0011_2 (+3)
,所以-B
的反码是1100_2
。
计算A + (-B的反码)
:
0101_2 (+5)
+ 1100_2 (-3的反码)
---------
10001_2
(这里产生了最高位进位)我们得到的这个结果
10001_2
,在 4 位系统中,最高位的1
是进位。如果直接舍弃,得到0001_2
,这不对,应该是0010_2
(+2)。
这个10001_2
实际上是16 + 1 = 17
。
我们刚才的数学公式是A - B + (2^N - 1)
。
5 - 3 + (2^4 - 1) = 2 + 15 = 17
。
所以,我们通过加法得到的结果是A - B
加上(2^N - 1)
。为什么需要“循环进位” (End-Around Carry)?
当A - B
是正数时,A - B + (2^N - 1)
的结果会大于2^N - 1
,从而产生一个最高位进位1
。
这个进位1
实际上代表的是2^N
。
而我们的结果A - B + (2^N - 1)
恰好比正确结果A - B
少了一个1
(因为多了一个2^N - 1
)。
所以,当出现最高位进位时,将这个进位1
加回到结果的最低位,就完美地弥补了那个“少掉的1
”,从而得到正确的结果。
A - B + (2^N - 1)
(原计算结果)
+ 1
(循环进位)
= A - B + 2^N
在模2^N
运算下,+ 2^N
等同于+ 0
。
所以最终结果就是A - B
。情况二:结果
A - B
为负数。
例如:3 - 5 = -2
(使用 4 位系统)
A = 0011_2 (+3)
B = 0101_2 (+5)
,所以-B
的反码是1010_2
。
计算A + (-B的反码)
:
0011_2 (+3)
+ 1010_2 (-5的反码)
---------
1101_2
(没有最高位进位)
这个结果1101_2
正是-2
的反码表示。
(2^N - 1) - 2 = 1111_2 - 0010_2 = 1101_2
。
所以,当结果为负数时,加法直接得到反码形式的正确答案,无需循环进位。 -
为什么叫“One's Complement” (1的补码/反码)?
因为一个数X
加上它的反码X_反
,结果总是N
个1
(即2^N - 1
)。
X + X_反 = X + ( (2^N - 1) - X ) = 2^N - 1
。
所以,反码可以理解为将自身“补足”到全1的那个数。
2. 补码 (Two's Complement) 的数学原理
-
表示负数: 在N位系统中,一个正数
X
的补码X_补
(表示-X
) 的数学定义是:
X_补 = 2^N - X
(在N位系统的模2^N
意义下)
这也就是“所有位取反再加1”操作的数学解释。
X_补 = ( (2^N - 1) - X ) + 1 = X_反 + 1
。
例如,对于 4 位系统,2^4 = 10000_2
(即 16)。
如果X = 0101_2 (+5)
:
-5
的补码就是(10000_2) - (0101_2) = 1011_2
。
(验证:0101_2
取反是1010_2
,再加1
得到1011_2
,符合定义。) -
加法运算: 我们的目标是计算
A - B
。在补码系统中,这被转换为A + (-B)
。
假设B
是一个正数,那么-B
的补码表示是2^N - B
。
所以,我们要计算的是A + (2^N - B)
。
重新整理一下:A - B + 2^N
关键:在模
2^N
运算下,+ 2^N
等同于+ 0
。
所以,A - B + 2^N ≡ A - B (mod 2^N)
。这意味着无论
A - B
是正数还是负数,简单的二进制加法就能直接得到A - B
的补码表示。- 如果
A - B
是正数: 结果就是A - B
本身(补码和原码相同)。
例如:5 - 3 = 2
(使用 4 位系统)
A = 0101_2 (+5)
B = 0011_2 (+3)
,所以-B
的补码是1101_2
。
计算A + (-B的补码)
:
0101_2 (+5)
+ 1101_2 (-3的补码)
---------
10010_2
(这里产生了最高位进位)
在 4 位系统中,最高位的进位1
(代表2^4
) 被自然舍弃。结果是0010_2
,即+2
,正确!
这就是为什么补码不需要“循环进位”! 那个进位1
正好对应了数学公式中被模除掉的2^N
。 - 如果
A - B
是负数: 结果就是A - B
的补码表示。
例如:3 - 5 = -2
(使用 4 位系统)
A = 0011_2 (+3)
B = 0101_2 (+5)
,所以-B
的补码是1011_2
。
计算A + (-B的补码)
:
0011_2 (+3)
+ 1011_2 (-5的补码)
---------
1110_2
(没有最高位进位)
这个结果1110_2
正是-2
的补码表示。
(验证:0010_2 (+2)
取反是1101_2
,加1
得到1110_2
。)
- 如果
-
为什么叫“Two's Complement” (2的补码/补码)?
因为一个数X
加上它的补码X_补
,结果总是2^N
。
X + X_补 = X + (2^N - X) = 2^N
。
在N位二进制系统中,2^N
表示为1
后面跟N
个0
(例如10000
for N=4)。这个最高位的1
会因为位数限制而被截断,留下N
个0
,也就是0
。
所以,补码可以理解为将自身“补足”到下一个2^N
(也就是模运算意义下的0
) 的那个数。
总结
- 反码 (One's Complement): 数学上是
(2^N - 1) - X
。加法结果是A - B + (2^N - 1)
。当结果为正时,会产生一个最高位进位1
,它代表2^N
。为了抵消公式中多出来的(2^N - 1)
,需要将这个1
(即2^N
) 加回到结果的最低位,这就正好抵消了2^N - 1
中的-1
,并利用了2^N
在模运算下等同于0
的特性,从而得到A - B
。 - 补码 (Two's Complement): 数学上是
2^N - X
。加法结果是A - B + 2^N
。由于在模2^N
运算下,+ 2^N
等同于+ 0
,所以这个2^N
(表现为最高位进位) 可以直接被丢弃,得到的结果就是A - B
的补码表示。这使得补码的加减运算硬件实现更简单、更高效,因此被现代计算机广泛采用。