背景
对使用 N 位补码表示的 x (x 是满足 −2N−1≤x≤2N−1−1 的整数)而言,如果依次进行如下两步操作
- 令 y=∼x (这里的 ∼ 表示按位取反)
- 令 z=y+1 (这里的 + 是 N 位无符号数的加法)
那么得到的 z 满足
- z=−x (如果x=−2N−1)
- z=x (如果 x=−2N−1)
本文会对此进行证明。
正文
补码和模运算
运用 模 N 运算(N 是正整数),我们可以将所有整数划分成 N 个等价类。以 N=16 为例,我们可以按照 xmod16 的结果把整数划分为如下 16 个等价类。
- 等价类 0: 如果 xmod16=0,则 x 属于这个等价类
- 等价类 1: 如果 xmod16=1,则 x 属于这个等价类
- 等价类 2: 如果 xmod16=2,则 x 属于这个等价类
- 等价类 3: 如果 xmod16=3,则 x 属于这个等价类
- 等价类 4: 如果 xmod16=4,则 x 属于这个等价类
- 等价类 5: 如果 xmod16=5,则 x 属于这个等价类
- 等价类 6: 如果 xmod16=6,则 x 属于这个等价类
- 等价类 7: 如果 xmod16=7,则 x 属于这个等价类
- 等价类 8: 如果 xmod16=8,则 x 属于这个等价类
- 等价类 9: 如果 xmod16=9,则 x 属于这个等价类
- 等价类 10: 如果 xmod16=10,则 x 属于这个等价类
- 等价类 11: 如果 xmod16=11,则 x 属于这个等价类
- 等价类 12: 如果 xmod16=12,则 x 属于这个等价类
- 等价类 13: 如果 xmod16=13,则 x 属于这个等价类
- 等价类 14: 如果 xmod16=14,则 x 属于这个等价类
- 等价类 15: 如果 xmod16=15,则 x 属于这个等价类
我在下图中将这 16 个等价类用 2 进制表示。

我们可以从这 16 个等价类中分别选择一个元素作为代表。一个简单的做法是,选择这个等价类中绝对值最小的非负数,即 0,1,2,3,⋯,15 作为代表。为了便于叙述,我们将这种做法称为 Solution1。而补码运算的选择与之不同,我们把补码运算从等价类中选择数字的方式称为 Solution2。Solution1 和 Solution2 的对比如下表所示 ⬇️
| 模 16 运算中的等价类 | Solution1 | Solution2 (补码运算的选择) | Solution1 和 Solution2 的选择相同吗? |
|---|
| 0000 | 0 | 0 | ✅ |
| 0001 | 1 | 1 | ✅ |
| 0010 | 2 | 2 | ✅ |
| 0011 | 3 | 3 | ✅ |
| 0100 | 4 | 4 | ✅ |
| 0101 | 5 | 5 | ✅ |
| 0110 | 6 | 6 | ✅ |
| 0111 | 7 | 7 | ✅ |
| 1000 | 8 | −8 | ❌ |
| 1001 | 9 | −7 | ❌ |
| 1010 | 10 | −6 | ❌ |
| 1011 | 11 | −5 | ❌ |
| 1100 | 12 | −4 | ❌ |
| 1101 | 13 | −3 | ❌ |
| 1110 | 14 | −2 | ❌ |
| 1111 | 15 | −1 | ❌ |
所以,对一个 N 位 2 进制数 x 而言,
- 当它的最高位(即
MSB, Most Significant Bit)是 1 时
- 如果我们分别把 x 看成 无符号数 xu 和 补码 xc,那么 xc=xu−2N
- 当它的最高位是 0 时
- 如果我们分别把 x 看成 无符号数 xu 和 补码 xc,那么 xc=xu
本文在举例时,均以 N=4 来进行说明。我们以 N=4 为例,看一看将各个 2 进制数视为 无符号数 和 补码 时的值是怎样的 ⬇️
| 4 位 2 进制数 x | 将其视为无符号数时的值 xu | 将其视为补码时的值 xc | xu→xc 的计算过程 (x 最高位为 0 时, xu=xc 总成立, 转化过程略) |
|---|
| 0000 | 0 | 0 | |
| 0001 | 1 | 1 | |
| 0010 | 2 | 2 | |
| 0011 | 3 | 3 | |
| 0100 | 4 | 4 | |
| 0101 | 5 | 5 | |
| 0110 | 6 | 6 | |
| 0111 | 7 | 7 | |
| 1000 | 8 | −8 | 8−16=−8 |
| 1001 | 9 | −7 | 9−16=−7 |
| 1010 | 10 | −6 | 10−16=−6 |
| 1011 | 11 | −5 | 11−16=−5 |
| 1100 | 12 | −4 | 12−16=−4 |
| 1101 | 13 | −3 | 13−16=−3 |
| 1110 | 14 | −2 | 14−16=−2 |
| 1111 | 15 | −1 | 15−16=−1 |
证明
我们可以把 x 的值分为 2 类 ⬇️
- 非负数: 0≤x≤2N−1−1
- 负数: −2N−1≤x≤−1
情形 1: 当 0≤x<2N−1 时
因为 x=0 的情况比较特殊,我们把它单独列出来,于是 0≤x<2N−1 可以细分为两种情形
- x=0
- 1≤x≤2N−1−1
情形 1.1 当 x=0 时
| 步骤 | 写成 N 位 2 进制数 | 将 2 进制数视为无符号数时的值 xu,yu,zu | 将 2 进制数视为补码时的值 xc,yc,zc | xu→xc yu→yc zu→zc 是如何计算的 |
|---|
| 初始值 x | x:N bit00⋯0 | xu=0 | xc=0 | xc=xu=0 |
| 取反 得到 y | y:N bit11⋯1 | yu=2N−1 | yc=−1 | (2N−1)−2N=−1 |
| 加一 得到 z | z:N bit00⋯0 | zu=0 | zc=0 | zc=zu=0 |
以 N=4 为例,取反和加一的过程如下图所示

情形 1.2 当 1≤x≤2N−1−1 时
y=∼x=(2N−1)−x
因为
1≤x≤2N−1−1
所以
2N−1≤y≤2N−2
所以 y 对应的 N 位 2 进制数的最高位是 1 ⬇️ (⋯ 表示这里不关心那些位的值)
y=1N-1 bit⋯
而 z=y+1,所以
2N−1+1≤z<2N−1
所以 z 对应 N 位 2 进制数的最高位是 1 ⬇️ (⋯ 表示这里不关心那些位的值)
z=1N-1 bit⋯
z 对应的补码 zc 所表示的值满足
=(2N−1−x)+1−2N
当 1≤x≤2N−1−1 时,计算步骤可以汇总成如下的表格 ⬇️
| 步骤 | 写成 N 位 2 进制数 | 将 2 进制数视为无符号数时的值 xu,yu,zu | 将 2 进制数视为补码时的值 xc,yc,zc | xu→xc yu→yc zu→zc 是如何计算的 |
|---|
| 初始值 x | x:0N-1 bit⋯ | xu=x | xc=x | xc=xu=x |
| 取反 得到 y | y:1N-1 bit⋯ | yu=(2N−1)−x | yc=−1−x | (2N−1)−x−2N=−1−x |
| 加一 得到 z | z:1N-1 bit⋯ | zu=2N−x | zc=−x | 2N−x−2N=−x |
以 N=4,x=5 为例,取反和加一的过程如下图所示

情形 2: 当 −2N−1≤x≤−1 时
因为 x=−2N−1 的情况比较特殊,我们把它单独列出来,于是 −2N−1≤x≤−1 可以细分为两种情形
- x=−2N−1
- −2N−1<x≤−1
情形 2.1 当 x=−2N−1 时
| 步骤 | 写成 N 位 2 进制数 | 将 2 进制数视为无符号数时的值 xu,yu,zu | 将 2 进制数视为补码时的值 xc,yc,zc | xu→xc yu→yc zu→zc 是如何计算的 |
|---|
| 初始值 x | x:1N-1 bit0⋯0 | xu=2N−1 | xc=−2N−1 | 2N−1−2N=−2N−1 |
| 取反 得到 y | y:0N-1 bit1⋯1 | yu=2N−1−1 | yc=2N−1−1 | yc=yu=y |
| 加一 得到 z | z:1N-1 bit0⋯0 | zu=2N−1 | zc=−2N−1 | 2N−1−2N=−2N−1 |
以 N=4 为例,取反和加一的过程如下图所示

情形 2.2 当 −2N−1<x≤−1 时
y=∼x=(2N−1)−(x+2N)=−x−1
因为
−2N−1<x≤−1
所以
0≤y<2N−1−1
所以 y 对应的 N 位 2 进制数的最高位是 0 ⬇️ (⋯ 表示这里不关心那些位的值)
y=0N-1 bit⋯
而 z=y+1,所以
1≤z<2N−1
所以 z 对应 N 位 2 进制数的最高位是 1 ⬇️ (⋯ 表示这里不关心那些位的值)
z=0N-1 bit⋯
z 对应的补码 zc 所表示的值满足
=(−x−1)+1
当 −2N−1<x≤−1 时,计算步骤可以汇总成如下的表格 ⬇️
当 1≤x≤2N−1−1 时,计算步骤可以汇总成如下的表格 ⬇️
| 步骤 | 写成 N 位 2 进制数 | 将 2 进制数视为无符号数时的值 xu,yu,zu | 将 2 进制数视为补码时的值 xc,yc,zc | xu→xc yu→yc zu→zc 是如何计算的 |
|---|
| 初始值 x | x:1N-1 bit⋯ | xu=x+2N | xc=x | (x+2N)−2N=x |
| 取反 变为 y | y:0N-1 bit⋯ | −yu=x−1 | yc=−x−1 | yc=yu=y |
| 加一 变为 z | z:0N-1 bit⋯ | zu=2N−x | zc=−x | 2N−x−2N=−x |
以 N=4,x=−3 为例,取反和加一的过程如下图所示

其他
文中展示取反和加一的过程的图是如何画出来的。我是 mermaid.live/ 上画图的。其中一张图的代码如下 ⬇️ (其他图的代码也是类似的,就不赘述了)
block
block
columns 5
header0["步骤 ⬇️"] name1["carry"] name0["bits"] u["当成无符号数"] c["当成补码"]
header1["(初始)"] carry0["0"] value0["1101"] u0["13"] c0["-3"]
header2["取反"] carry1["0"] value1["0010"] u1["2"] c1["2"]
header3["加一"] carry2["0"] value2["0011"] u2["3"] c2["3"]
end
style header0 fill:#080;
style header1 fill:#0f0;
style header2 fill:#0f0;
style header3 fill:#0f0;
style value0 fill:#ff0;
style carry0 fill:#0ff;
style value1 fill:#ff0;
style carry1 fill:#0ff;
style value2 fill:#ff0;
style carry2 fill:#0ff;
style name0 fill:#d3d3d3,stroke:#fff;
style name1 fill:#088,stroke:#fff;
style u fill:#f80;
style c fill:#f80;
style u0 fill:#f80;
style c0 fill:#f80;
style u1 fill:#f80;
style c1 fill:#f80;
style u2 fill:#f80;
style c2 fill:#f80;

参考资料