在大屏或电脑上体验更佳——动画和操作区域更宽敞。
输入

二进制补码

补码是计算机表示有符号整数的常用编码。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 取负,画布用三行展示原模式、反码和结果。

  1. 建立八列,权重从左到右为 −128、64、32、16、8、4、2、1。没有另加的负号位置。
  2. 写出绝对值 |x| = 00000100,只设置权重 4 的位置。
  3. 每位取反,得到 11111011。各列独立翻转,这是反码,还不是 −4。
  4. 加 1。末尾两个 1 依次清零、传递进位,第 2 位吸收进位,得到 11111100。若对 5 取负,反码 11111010 的最低位已经为 0,不会产生这段连续进位。
  5. 先加正权重:第 6、5、4、3、2 位合计 64+32+16+8+4=124。若误按无符号解释整个字,会得到 252。
  6. 计入最高位的 −128,得到 124−128=−4。
  7. 完成。对 11111100 再取反加 1,又得到 4,验证取负运算。

当前构建行保持高亮,完成行作为参照淡化。列下方的正权重一起点亮,随后负的最高位权重加入,完成求和。

为什么一个加法器就够用

切到 add,默认计算 a = 5(00000101)与 b = −3(11111101)。普通串行进位加法器从第 0 位向上处理:

位两个输入加进位写入输出进位
01+1+0=201
10+0+1=110
21+1+0=201
3–6每列 0+1+1=201
70+1+1=201

结果 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 成为大正数;精确转换取决于类型等级与表示范围,不能默认保留有符号顺序。