二进制补码
补码是计算机表示有符号整数的常用编码。n 位字的最高位权重从 +2ⁿ⁻¹ 改为 −2ⁿ⁻¹,因此八位范围是 −128 到 127。取负时逐位取反再加 1,减法便可转成加法。
补码让无符号加法器也能处理有符号模式,不必先按符号分支。相比把最高位单独当正负标记的原码,补码把符号纳入位值规则。加法器仍按列相加;数学结果能装入范围时,模式就是正确的有符号结果,否则要检测溢出。
最高位也是位值
无符号的 10110101 表示 128+32+16+4+1=181。补码只改变最高位权重,其他位置不变:第 6 位仍是 64,第 0 位仍是 1。
于是 11111100 表示 −128+64+32+16+8+4=−4。负数最高位都是 1,因为只有这一位能把总和拉到零以下。但它仍是参与求和的位值,不是可独立翻转的符号旗标。
对任意八位模式 x,逐位反转得到 ~x,两者每列恰好有一个 1,所以 x+~x=11111111,补码解释为 −1。在固定位宽的模运算下整理得到 ~x+1=−x。最小负数的数学相反数装不下,位模式会回到自身。
逐列加法只处理两个输入位和一个进位:写低位,把高位传给下一列。它不检查符号。超出最高列的进位被丢弃,留下模 2ⁿ 的低 n 位;有符号结果超范围则属于另一项溢出判断。
逐步演示
默认在八位中对 x = 4 取负,画布用三行展示原模式、反码和结果。
- 建立八列,权重从左到右为 −128、64、32、16、8、4、2、1。没有另加的负号位置。
- 写出绝对值
|x| = 00000100,只设置权重 4 的位置。 - 每位取反,得到
11111011。各列独立翻转,这是反码,还不是 −4。 - 加 1。末尾两个 1 依次清零、传递进位,第 2 位吸收进位,得到
11111100。若对 5 取负,反码11111010的最低位已经为 0,不会产生这段连续进位。 - 先加正权重:第 6、5、4、3、2 位合计
64+32+16+8+4=124。若误按无符号解释整个字,会得到 252。 - 计入最高位的 −128,得到
124−128=−4。 - 完成。对
11111100再取反加 1,又得到 4,验证取负运算。
当前构建行保持高亮,完成行作为参照淡化。列下方的正权重一起点亮,随后负的最高位权重加入,完成求和。
为什么一个加法器就够用
切到 add,默认计算 a = 5(00000101)与 b = −3(11111101)。普通串行进位加法器从第 0 位向上处理:
| 位 | 两个输入加进位 | 写入 | 输出进位 |
|---|---|---|---|
| 0 | 1+1+0=2 | 0 | 1 |
| 1 | 0+0+1=1 | 1 | 0 |
| 2 | 1+1+0=2 | 0 | 1 |
| 3–6 | 每列 0+1+1=2 | 0 | 1 |
| 7 | 0+1+1=2 | 0 | 1 |
结果 00000010=2,符合 5+(−3)=2。没有执行另一套减法规则。最高位外的进位被丢弃,结果依然正确,所以丢弃进位本身不是错误。
有符号溢出检测:进入最高位的进位与离开最高位的进位不同。上例两者都是 1,无溢出。Overflow: 127 + 1 中进入进位为 1,输出进位为 0,数学和 128 装不进八位有符号范围,留下的 10000000 被解释为 −128。
动画显示溢出比较但不触发异常。硬件状态位与语言检查规则各异,不能把有符号溢出一律当成静默回绕。轨迹显式把中间结果限制在 n 位,以模拟固定字宽。寄存器保存低位,但语言语义仍须遵守:C 有符号溢出未定义,Rust 可能检查并 panic。
边界情况
- 范围不对称:八位有 −128,没有 +128。对 −128 的模式取反加 1,仍是
10000000。这不表示数学绝对值为负,而表示 +128 无法放入同一类型,不能假设abs(INT_MIN)总能返回有效正值。 - 零只有
00000000一种编码。反码与原码有另一种“负零”,比较时需把两个零视为相等;补码省去这一区分。 - 输出进位不等于溢出:
5+(−5)有输出进位却正确得到 0;127+1没有输出进位,却发生有符号溢出。 - 四位预设范围为 −8 到 7,
7+1留下 −8。规则不变,只是范围缩小。 - 负数右移要区分算术与逻辑右移。算术右移补符号位,逻辑右移补零;对非零右移量,后者可把负模式解释成非负值。实际操作由语言与类型决定。
常见错误
- 把最高位当独立符号标记。只翻最高位是原码式思路,不是补码取负;补码要全部取反再加 1。
- 停在反码。漏掉加 1,
11111011是 −5,而非 −4。 - 仅用输出进位判溢出,会误报
5+(−5),漏报127+1。应比较最高位的输入与输出进位。 - 假定语言保证有符号回绕。C、C++ 的有符号溢出是未定义行为,优化器可以假设它不发生,像
if (x + 1 < x)这样的溢出检测可能被消除。 - 比较时混用有符号和无符号类型。常见平台上
int与size_t比较会把有符号一方转成无符号,−1 成为大正数;精确转换取决于类型等级与表示范围,不能默认保留有符号顺序。