原码、反码和补码是很重要的入门内容,以下是一些学习的笔记。
为什么要有"机器数"
我们平时写的是带正负号的十进制数,但硬件里只有 0/1,所以符号也得用 0/1 来编码。把带符号的真值(如 +5、−5)变成能在机器里存储的一串 0/1,就是机器数。历史上出现过三种主要编码:原码、反码、补码。
下面统一讨论 n 位二进制:最高位是符号位(0 代表正、1 代表负),低 位是数值位。
一、原码(Sign–Magnitude)
定义最简单直观:
- 正数:符号位
0+ 真值的绝对值二进制; - 负数:符号位
1+ 真值的绝对值二进制。
写成式子就是:
例子(8 位):+5 → 0000 0101;−5 → 1000 0101
原码的优点是直观,看符号位就知道正负、读真值容易。缺点是:
- 0 有两种表示:+0 =
0000 0000,−0 =1000 0000,判断是否为零要额外处理; - 做加减法得先比较符号,再决定是加还是减、谁减谁,符号位不能直接参与运算,电路很麻烦。
于是有了反码,再到补码。
二、反码(Ones’ Complement)
定义:
- 正数:反码 = 原码(符号位
0,数值位照抄); - 负数:符号位保持
1,数值位逐位取反(0↔1)。
所以反码又叫"对 1 取补",负数反码满足:
例子(8 位):−5 的原码是 1000 0101,数值位取反 → 1111 1010。
反码的运算有两个麻烦:减法虽能转成加法,但最高位的端进位(end-around carry)要绕回最低位再加一次;而且 0 仍然有两种表示(+0 = 0000 0000,−0 = 1111 1111)。
三、补码(Two’s Complement)——重点
定义:
- 正数:补码 = 原码 = 反码;
- 负数:在反码基础上 +1;等价地 。
统一写成对模 取余:
例子(8 位)求 −5 的补码:
- 取反 →
1111 1010(这就是反码) - +1 →
1111 1011
为什么补码能把减法变成加法?
n 位机器数其实是在模 下计数的:加了 溢出丢掉的进位,等价于没加。对负数有:
于是 ()可以这样变形:
右边第二项 正是 。也就是说:减一个数 = 加上它的补码,硬件里只要有一个加法器就够了——这是补码最大的价值。
顺带验证一下"负数取反加一就是补码":把 n 位 逐位取反得到 ,再 +1 得 ,成立 ✓
补码的优点
- 0 唯一:+0 与 −0 的补码都是
0000 0000; - 符号位直接参与运算,无需判断正负、无需比较大小;
- 范围多一个负数:能表示 ,而原码/反码最小只到 。
数值范围对比(以 8 位为例)
| 编码 | 表示范围 | 0 的表示 |
|---|---|---|
| 原码 | −127 ~ +127 | +0、−0 两种 |
| 反码 | −127 ~ +127 | +0、−0 两种 |
| 补码 | −128 ~ +127 | 唯一(0000 0000) |
8 位补码的最小值 1000 0000 = −128 很特殊:符号位为 1 且数值位全 0,它占用的是原码里"−0"那个闲置位置,表示 。对它做"取反加一"得到的仍是它自己,所以没有对应的正数 +128。
四、三种编码速查表(8 位)
| 真值 | 原码 | 反码 | 补码 |
|---|---|---|---|
| +5 | 0000 0101 |
0000 0101 |
0000 0101 |
| −5 | 1000 0101 |
1111 1010 |
1111 1011 |
| +0 | 0000 0000 |
0000 0000 |
0000 0000 |
| −0 | 1000 0000 |
1111 1111 |
(与 +0 相同) |
| −127 | 1111 1111 |
1000 0000 |
1000 0001 |
| −128 | 不可表示 | 不可表示 | 1000 0000 |
快速互转口诀(只对负数生效,正数三种码一样):
- 原码 ↔ 反码:数值位取反 / 再取反;
- 反码 ↔ 补码:+1 / −1;
- 求任意数的相反数:补码按位取反再加 1。
五、由补码读真值
两种方法任选:
- 取反加一:符号位为 1 → 数值位取反 +1 得到绝对值,再添上负号。
- 例:
1111 1011→ 取反0000 0100→ +1 =0000 0101= 5 → 真值 −5 ✓
- 例:
- 按权展开(推荐,更快):最高位的权当作 ,其余位照常取正权。
- 例:4 位
1101= - 例:8 位
1000 0000= ,一眼看出。
- 例:4 位
六、补码加减法举例(8 位)
例 1:5 − 3
丢掉最高位的进位 → 0000 0010 = 2 ✓(在模 下这个进位本来就该丢,不算错)
例 2:−5 + 6
七、溢出判断(Overflow)
直观理解:两个同号数相加,结果的符号却与加数相反,就溢出了。因为减法本质是加补码,所以被减数与减数异号时也要留意。
电路上最可靠的判据是:进位到符号位的进位 与符号位产生的进位 不同,即溢出(等价于双符号位法):
溢出例子(8 位补码):
- :(=−126),两个正数加出负数 → 溢出;
- :(=+126),两个负数加出正数 → 溢出。
反例(未溢出):(=−90)✓
注意区分:最高位那个被丢掉的进位 1 不是错误(模 计数本来就该丢);只有结果符号不对(超出可表示范围)才是溢出。
八、扩展与截断
- 符号扩展(sign extension):把补码从窄位宽变宽时,高位一律补符号位。
- 例:8 位 −5 =
1111 1011→ 16 位1111 1111 1111 1011,数值不变。
- 例:8 位 −5 =
- 零扩展:仅用于无符号数(或直接把高位置 0),不要把无符号数和补码混用。
- 截断:直接砍掉高位。若被砍掉的部分等于"用符号位补齐"的那一串(即原数能装进低位),数值不变;否则说明超范围,会出错。
- 例:16 位 −5 =
1111...1111 1011截成 8 位1111 1011,仍是 −5 ✓
- 例:16 位 −5 =
九、数字电路里怎么做减法?
有了补码,加减法可以共用同一个加法器。要算 ,只需送 和 ,并让加法器最低位的进位输入为 1:
硬件上用一个控制信号 Sub(做减法时为 1):
- 每一位的 先经过一个异或门取反:;
Sub同时接到加法器最低位的进位输入Cin;Sub = 0时做加法,Sub = 1时做减法。
加法器会额外输出标志位:Carry(最高位进位)、Overflow()、Zero、Negative。现代 CPU / ALU 的加减法基本都是这个思路——这也是补码在硬件里真正"好用"的原因。
小结
一句话记住三者:
- 原码:看真值最直观,但有 ±0、减法麻烦;
- 反码:负数取反,能"减转加",却有循环进位和 ±0;
- 补码 = 反码 + 1:0 唯一、符号位直接参与运算、范围多一个 、加减法统一成加法——机器真正使用的就是补码。