补码(Two's Complement)是计算机中表示有符号整数最常用的方法。理解补码是学习计算机底层、数字电路、芯片设计的基础。
一、为什么需要补码?
计算机中数据用二进制表示,但二进制本身没有"正负"的概念。为了表示负数,历史上有过三种方案:
| 原码 | 直观(最高位表示符号) | 有 +0 和 -0 两个零;加减法需要额外判断 |
| 反码 | 加减法稍简化 | 仍有 +0 和 -0;加减法仍需特殊处理 |
| 补码 | +0 和 -0 统一;加减法可以用同一套加法器电路实现 | 略不直观 |
核心好处: CPU 只需要一个加法器,就能完成加法和减法,硬件电路大大简化。
二、补码的计算规则
对于正数和零
正数的补码 = 原码本身(最高位是符号位 0)
对于负数
负数的补码 = 反码 + 1 = 原码取反(含符号位除外)后 + 1
具体三步法:
或者一个更快的口诀:从右往左数,遇到第一个 1 之前的所有位保持不变,第一个 1 也不变,之后的位全部取反。
三、详细例子(8 位补码为例)
8 位补码能表示的范围:-128 ~ +127
例 1:+5 的 8 位补码
正数补码 = 原码:
+5 的原码: 0000 0101
+5 的补码: 0000 0101 ← 直接就是
例 2:-5 的 8 位补码(重点)
方法一:三步法
步骤 1: 写出 |-5| = 5 的原码: 0000 0101
步骤 2: 按位取反(反码): 1111 1010
步骤 3: 末位加 1(补码): 1111 1011
所以 -5 的 8 位补码 = 1111 1011
方法二:口诀法(从右往左找第一个 1)
原码: 0000 0101
↑ 从右往左,第一个 1 在最低位
保持这个 1 和它右边不变(这里它就是最低位,没有右边)
左边所有位取反
补码: 1111 1011
例 3:-1 的 8 位补码
Apply
步骤 1: |−1| = 1 的原码: 0000 0001
步骤 2: 按位取反: 1111 1110
步骤 3: 末位 + 1: 1111 1111
所以 -1 的 8 位补码 = 1111 1111(全是 1,很好记)
例 4:-128 的 8 位补码(边界情况)
步骤 1: |−128| = 128 的原码: 1000 0000(恰好 8 位)
步骤 2: 按位取反: 0111 1111
步骤 3: 末位 + 1: 1000 0000
所以 -128 的 8 位补码 = 1000 0000
注意: 8 位补码能表示 -128(最小负数)但不能表示 +128,所以补码的表示范围是不对称的:[-128, +127]。
例 5:-20 的 8 位补码
步骤 1: |−20| = 20 的原码: 0001 0100
步骤 2: 按位取反: 1110 1011
步骤 3: 末位 + 1: 1110 1100
所以 -20 的 8 位补码 = 1110 1100
用口诀法验证:
原码 20: 0001 0100
↑ 从右往左第一个 1(位 2)
保持它和右边不变 → 100
左边所有位取反 → 1110 1
补码: 1110 1100 ✓
四、补码的"反向解读":如何从补码读出十进制
规则:
- 如果最高位是 0:正数,按普通二进制读
- 如果最高位是 1:负数,对补码再做一次"取反 + 1",得到的就是它的绝对值
例 6:补码 1111 1011 是多少?
最高位是 1,是负数
取反: 0000 0100
+1: 0000 0101
对应十进制: 5
所以原数是: -5 ✓(验证了例 2)
例 7:补码 1000 0001 是多少?
最高位是 1,是负数
取反: 0111 1110
+1: 0111 1111 = 127
所以原数是: -127
五、补码的精髓:用加法做减法
这是补码最神奇的地方。下面用例子展示。
例 8:计算 7 – 5(即 7 + (-5))
把减法转换为补码加法:
7 的补码: 0000 0111
-5 的补码: 1111 1011
─────────────
相加: 1 0000 0010
↑
第 9 位的进位被丢弃(8 位寄存器溢出)
最终结果: 0000 0010 = +2 ✓
完美! 用加法器实现了减法,硬件无需专门的减法器电路。
例 9:计算 -3 + (-5)
-3 的补码: 1111 1101
-5 的补码: 1111 1011
─────────────
相加: 1 1111 1000
↑
第 9 位进位丢弃
最终结果: 1111 1000
验证: 最高位是 1,是负数
取反: 0000 0111
+1: 0000 1000 = 8
原数: -8 ✓
六、不同位宽的补码
补码的位宽决定了表示范围:
| 4 位 | -8 ~ +7 | 1111 | 1000 (-8) |
| 8 位 | -128 ~ +127 | 1111 1111 | 1000 0000 (-128) |
| 16 位 | -32768 ~ +32767 | 0xFFFF | 0x8000 (-32768) |
| 32 位 | -2³¹ ~ +2³¹-1 | 0xFFFF_FFFF | 0x8000_0000 |
| 64 位 | -2⁶³ ~ +2⁶³-1 | 0xFFFF_FFFF_FFFF_FFFF | 0x8000_0000_0000_0000 |
例 10:-5 的 16 位补码
步骤 1: |−5| = 5 的 16 位原码: 0000 0000 0000 0101
步骤 2: 按位取反: 1111 1111 1111 1010
步骤 3: 末位 + 1: 1111 1111 1111 1011
简写: 0xFFFB
七、补码的几个重要性质
| +0 和 -0 唯一 | 0 的补码只有一个 0000 0000 | 取反 1111 1111 +1 → 1 0000 0000,丢弃进位 → 0000 0000 |
| 最小负数没有正对应 | 8 位中 -128 存在,但 +128 不能表示 | 范围 [-128, +127] 不对称 |
| 符号位扩展 | 高位补符号位本身(正数补 0,负数补 1)值不变 | 8 位 1111 1011 (-5) → 16 位 1111 1111 1111 1011 (-5) |
| 取反 + 1 是自反操作 | 对补码再做"取反 + 1"得到相反数 | -5 补码取反+1 → +5 |
例 11:符号位扩展(重要!)
把 8 位的 -5 扩展为 16 位:
8 位 -5 补码: 1111 1011
↑ 符号位是 1,高位全补 1
16 位 -5 补码: 1111 1111 1111 1011
└────────┘ 新增的 8 位全是 1
而把 8 位的 +5 扩展为 16 位:
8 位 +5 补码: 0000 0101
↑ 符号位是 0,高位全补 0
16 位 +5 补码: 0000 0000 0000 0101
这就是 CPU/Verilog 中的"有符号扩展"(sign extension),在指令集设计中极其常见。
八、Verilog 中的补码运算示例
module twos_comp_demo;
reg signed [7:0] a, b;
reg signed [7:0] result;
initial begin
a = 8'd7; // +7 的补码: 0000_0111
b = -8'd5; // -5 的补码: 1111_1011(编译器自动转)
result = a + b; // 7 + (-5) = 2
$display("a = %0d (binary: %b)", a, a);
$display("b = %0d (binary: %b)", b, b);
$display("a + b = %0d (binary: %b)", result, result);
end
endmodule
输出:
a = 7 (binary: 00000111)
b = -5 (binary: 11111011)
a + b = 2 (binary: 00000010)
九、综合速查表(8 位补码)
| +127 | 0111 1111 | 0x7F |
| +64 | 0100 0000 | 0x40 |
| +1 | 0000 0001 | 0x01 |
| 0 | 0000 0000 | 0x00 |
| -1 | 1111 1111 | 0xFF |
| -2 | 1111 1110 | 0xFE |
| -64 | 1100 0000 | 0xC0 |
| -127 | 1000 0001 | 0x81 |
| -128 | 1000 0000 | 0x80 |
十、一句话总结
正数的补码 = 它自己;负数的补码 = 绝对值取反 + 1。补码让计算机用一套加法器实现加减法,是现代计算机和芯片设计的基石。
需要的话,我可以进一步讲解:
- 浮点数的表示(IEEE 754)
- 补码乘法(Booth 算法)
- 在 Verilog 中实现有符号运算的注意事项
- 溢出检测(overflow / carry 区别)

