在计算机科学和数字电路中,"补码" (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(例如10000forn=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(例如10000for 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的补码表示。这使得补码的加减运算硬件实现更简单、更高效,因此被现代计算机广泛采用。