Appearance
数据的表示:原码、反码、补码、移码
概念
计算机里所有数最终都变成一串 0 和 1,但同一串 01 可以有两种读法:当作无符号数读,也可以当作有符号数读。"原码、反码、补码、移码"就是有符号数的四种编码约定(coding scheme)——它们回答的是同一个问题:符号怎么放进二进制里。
四者的分工很清楚:
| 码制 | 主要用途 |
|---|---|
| 原码 | 直观,人读得懂;不参与运算 |
| 反码 | 求补码的中间过渡 |
| 补码 | 机器的实际运算形式(加减法统一用加法实现) |
| 移码 | 浮点数的阶码(因为移码比较大小与无符号比较一致) |
一句话抓住重点:原码给人看,补码给机器算,移码给阶码比大小。 408 的真主战场是补码。
原理
一、真值与机器数
- 真值(true value):带正负号的数,如
、 。它是人脑子里的数。 - 机器数(machine number):真正存进寄存器的那串 01,如
11011011。它没有"-"号可用,只能牺牲最高位当符号位。
设字长为
二、四种码制的定义(设 , )
| 码制 | 正数 | 负数 |
|---|---|---|
| 原码(sign-magnitude) | 符号位 0 + 绝对值 | 符号位 1 + 绝对值 |
| 反码(ones' complement) | 同原码 | 符号位不变,数值位逐位取反 |
| 补码(two's complement) | 同原码 | 反码 + 1 |
| 移码(biased / offset) | 符号位取反的补码 | 符号位取反的补码 |
移码还有个更常用的等价定义:
即把真值整体平移
三、表示范围( ,必须背下来)
| 码制 | 范围 | 个数 | 零的表示 |
|---|---|---|---|
| 原码 | 255 | 两个:0000 0000(+0)、1000 0000(−0) | |
| 反码 | 255 | 两个:0000 0000(+0)、1111 1111(−0) | |
| 补码 | 256 | 唯一:0000 0000 | |
| 移码 | 256 | 唯一:1000 0000 |
为什么补码能多表示一个数:原码和反码有两个零,白白浪费一个编码;补码把唯一的零编码让出来,于是多出 1000 0000 这个位置表示
四、码制之间的转换
四种码制互相转换,记住三条通路:
负数
原码 ──────────→ 反码 ──────────→ 补码
(符号位1+绝对值) (数值位取反) (+1)
↑ │
└──────── 移码 = 补码符号位取反 ←──┘- 原码 → 反码:负数符号位不动,数值位全取反(正数不变)。
- 反码 → 补码:加 1(正数不变)。
- 补码 → 移码:符号位取反,其余位不动。
负数补码的两种手算技巧(都要会,考场上挑快的用):
- 取反加一:先写绝对值的原码,数值位取反,再 +1。
- 末 1 法:从右往左找第一个 1,这个 1 和它右边的所有 0 保持不动,左边(含符号位)全部取反。
以 0010 0101,最右边的 1 在第 0 位,所以右侧不动、左侧取反 → 1101 1011。与"取反加一"结果一致。
五、补码的本原:模运算
补码不是人为规定的技巧,它来自模运算(modular arithmetic)。
在这个模意义下:
所以 1101 1011。减法
补码还有一个实用的位权公式,可以直接把机器数算回真值:
即最高位的权是负的。验证 1101 1011:
六、加减运算与溢出判断
补码加减法规则只有一句:
符号位照常参与运算,产生的高位进位直接丢弃。
溢出的真正判据:结果超出了补码的表示范围。机器上有三种判法:
| 判法 | 规则 |
|---|---|
| 双符号位法 | 用 00 表示正、11 表示负。结果 01 = 正溢出,10 = 负溢出(两个符号位不等即溢出) |
| 进位比较法 | 记最高位进位 |
| 同号法 | 两个正数相加得负数,或两个负数相加得正数 → 溢出。异号相加永不溢出 |
三种判法是等价的,但**"最高位有进位"本身不是溢出**——这一点几乎年年考。
七、符号扩展
把
| 码制 | 扩展规则 |
|---|---|
| 补码 | 高位全部填符号位(正数补 0,负数补 1) |
| 反码 | 同上(正数补 0,负数补 1) |
| 原码 | 高位填 0,只在符号位与数值位之间插 0 |
8 位: 1101 1011
16 位: 1111 1111 1101 1011
32 位: 1111 1111 1111 1111 1111 1111 1101 1011示例
例 1:求 的四种码( )
完整计算过程:
第一步,写出绝对值
第二步,原码 = 符号位 1 + 7 位绝对值:
第三步,反码 = 符号位不变,数值位逐位取反(0100101 → 1011010):
第四步,补码 = 反码 + 1:
第五步,移码 = 补码符号位取反(1 → 0):
验算(用位权公式与偏移定义两边对一下):
- 补码按位权:
✓ - 移码按定义:
✓
答案表:
| 码制 | 机器数 | 十进制 |
|---|---|---|
| 原码 | 1010 0101 | — |
| 反码 | 1101 1010 | — |
| 补码 | 1101 1011 | 219 |
| 移码 | 0101 1011 | 91 |
例 2:用补码算
完整计算过程:
第一步,写
第二步,写 11011001,加 1:
第三步,相加(符号位一起参加):
0001 1001 (+25)
+ 1101 1010 (-38)
────────────
1111 0011最高位没有产生进位,无需丢弃。
第四步,把结果算回真值:
结论:
例 3:溢出判断( )
| 运算 | 机器码相加 | 真值和 | 是否溢出 | |||
|---|---|---|---|---|---|---|
0111 + 0001 = 1000 | 0 | 1 | 1 | 8 | 溢出 | |
0101 + 0011 = 1000 | 0 | 1 | 1 | 8 | 溢出 | |
0101 + 0101 = 1010 | 0 | 1 | 1 | 10 | 溢出 | |
0011 + 0100 = 0111 | 0 | 0 | 0 | 7 | 不溢出 |
0111 的最低位两个 1 相加产生进位,进位一路传到最高位——
用同号法复核:1000 读作
例 4:C 代码——观察位模式
本机不提供代码运行能力,下面代码加了 runnable 标记仅为将来接入运行件预留;现在请自行在本地编译验证。
#include <stdio.h>
#include <stdint.h>
/* 打印整数 x 在 n 位下的二进制(不含符号位区分,纯粹位模式) */
static void print_bits(uint32_t x, int n) {
for (int i = n - 1; i >= 0; i--) {
putchar((x >> i) & 1u ? '1' : '0');
if (i % 4 == 0 && i != 0) putchar(' ');
}
putchar('\n');
}
int main(void) {
uint8_t v = 37; /* 用无符号承载位模式,避开有符号溢出 */
printf("-37 原码: ");
print_bits((uint8_t)(0x80u | v), 8); /* 1010 0101 */
printf("-37 反码: ");
print_bits((uint8_t)(0x80u | (uint8_t)~v), 8); /* 1101 1010 */
printf("-37 补码: ");
print_bits((uint8_t)(0x100u - v), 8); /* 1101 1011 */
/* 补码加法:25 + (-38),8 位截断 */
uint8_t a = 25, b = (uint8_t)(256 - 38);
uint8_t s = (uint8_t)(a + b);
printf("25 + (-38) = ");
print_bits(s, 8); /* 1111 0011 */
printf("解释为有符号数 = %d\n", (int)(int8_t)s); /* -13 */
/* 符号扩展:把 8 位 -37 升到 16 位 */
int8_t small = -37;
int16_t wide = small; /* C 自动做符号扩展 */
printf("符号扩展后 16 位 = %d\n", wide); /* -37 */
return 0;
}
c 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
关键点:用 uint8_t 承载位模式,用 int8_t 重新解释。 直接对 int8_t 做
例 5:Python 对照
def bits(v, n):
return format(v & ((1 << n) - 1), '0%db' % n)
def group(s):
return ' '.join(s[i:i+4] for i in range(0, len(s), 4))
n = 8
MASK = (1 << n) - 1
# 原码 / 反码 / 补码 / 移码
x = -37
mag = bits(abs(x), n - 1)
yz = ('1' if x < 0 else '0') + mag
fan = ('1' + ''.join('1' if c == '0' else '0' for c in mag)) if x < 0 else yz
bu = bits(x, n) # Python 负数天然是补码语义
yi = bits(x + (1 << (n - 1)), n) # 移码 = 真值 + 2^(n-1)
print('原码', group(yz))
print('反码', group(fan))
print('补码', group(bu))
print('移码', group(yi))
# 位权公式复核补码
val = -int(bu[0]) * 2**(n-1) + int(bu[1:], 2)
print('位权公式算回真值 =', val)
# 补码减法:25 - 38
print('25 - 38 =', int(bits(25 + (-38), n), 2) - 256)
# 溢出判断:4 位
for a, b in [(7, 1), (5, 3), (3, 4), (-4, -5)]:
r = a + b
print('%3d + %3d = %3d 溢出=%s' % (a, b, r, not (-8 <= r <= 7)))
python 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
输出对照:补码 1101 1011、位权公式算回真值 = -37、25 - 38 = -13,与例 1、例 2 完全一致。
考点
考点
1. 必背表(
| 码制 | 范围 | 零 |
|---|---|---|
| 原码 | 两个 | |
| 反码 | 两个 | |
| 补码 | 一个 | |
| 移码 | 一个(1000 0000) |
2. 高频陷阱
- 补码的
是"孤儿":它没有对应的原码和反码。原码里的1000 0000表示的是 ,不是 。题目问"某码制能表示的最小数"时,原码答 、补码答 。 - 反码的
是1111 1111,与补码的 长得一模一样。看到1111 1111先问一句"这是哪种码"。 - 移码的 0 是
1000 0000,不是0000 0000。移码不用于表示整个数据,只用于浮点数的阶码。 - 移码比较大小 = 无符号比较:这正是选它做阶码的原因——浮点数比较大小可以先比阶码。若把移码当补码看会全错。
- 溢出 ≠ 最高位有进位。正确判据是
。反例: 在 4 位下 (没进位)却仍然溢出。 - 异号相加永不溢出。用这条快速排除选项。
- 符号扩展对原码特殊:原码不能在高位补符号位,只能在符号位与数值位之间插 0。
- 无符号数与有符号数共用一串 01:
1111 1111当无符号读是 255,当补码读是 。题目说"无符号数"时必须换算法则,此时溢出看最高位进位 CF,而不是 OF。
3. 计算提速口诀
- 负数补码:末 1 及右侧不变,左侧全反。
- 补码求负:全部位取反再加 1(对自己取反加一就得到相反数)。
- 判断两数是否同号:符号位异或,0 表示同号。
- 求真值:最高位带负权,其余位正常加权。
4. 与后续章节的接口
- 补码加法器就是第 04 篇的行波进位 / 先行进位加法器,加减法统一通路在那一篇展开。
- 移码是第 03 篇浮点数阶码的编码方式,为什么阶码用移码,答案就是上文的"比较大小"。
小结
- 原码符号位 + 绝对值,直观但不便运算,有双零。
- 反码是求补码的桥,同样有双零。
- 补码是机器的运算形式,本质是模
运算,把减法化成了加法;范围 ,零唯一。 - 移码就是补码符号位取反,专供阶码使用,好处是"比大小即比机器数"。
- 三个必须张口就来的点:范围表、零的表示、溢出判据
。
下一篇:浮点数与 IEEE 754
评论(0)
当前浏览器不允许本地存储,评论无法保存。
还没有评论,来说两句。