很久以前,集市上有一位账房先生老陈,每天要记两类账:老张今天进了五两银子、老王赊了三两银子。可他的账本上全是只有 0 和 1 的“码纸”,怎么区分“进账”与“欠账”?他试着把第一张码纸当作旗子——旗子竖起来代表欠账,放下代表进账,这就是原码;可算起加减法来要翻来覆去地看旗子,累得腰酸背痛。后来他发现,与其费劲算减法,不如绕一个方向加回去——就像钟表上,五点往回拨三格,跟往前拨十三格到达同一地方!这就是补码。再加上专门给浮点数“量温度”用的移码,四种记账法构成了计算机里所有数字的底层语言。今天我们就跟着老陈,把这四种记账法一次读懂。

岗位关联

理解这四种编码,等于拿到理解 CPU 加减电路、溢出报错、浮点精度的万能钥匙——这是数字电路与底层系统面试的必考点,也是读懂汇编与反编译代码的底层直觉。

目录
  1. 可视化演示
  2. 二进制与位宽:码纸的底牌
  3. 原码记账法:最直观的记账
  4. 反码过渡:翻面记账法
  5. 补码奇迹:钟表绕圈
  6. 溢出判定:拨过十二点
  7. 移码与浮点:量温度的尺子
  8. 编码互转:一本账四种写法
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示计算机四种数值编码:原码(符号位+绝对值)、反码(负数取反)、补码(反码+1,统一加减法)、移码(补码符号位翻转,用于浮点阶码)。三种模式对比编码值、转换关系和运算统一性。

本节配有交互动效(进入视口后自动加载,约 3KB)。

四种编码对比 原码:最高位为符号位,其余为绝对值,直观但有+0/-0问题 反码:负数数值位取反,仍有双零问题 补码:反码+1,零唯一,统一加减法运算 补码 = 反码 + 1, 移码 = 补码符号位取反 四种编码各有适用场景,补码是主流。

二进制与位宽:码纸的底牌

老陈拿到了第一叠码纸,每一张只能写 0 或 1。如果只有 3 张码纸,他能记下多少种账?从 000 数到 111,一共 8 种——这就是位宽的“底牌”:n 张码纸,底牌共有 2 的 n 次方种组合。码纸越多,能记的数就越大,但这套系统只能记正数,遇到赊账就束手无策。

计算机内部没有十进制,只有这种“码纸”——二进制位(bit)。位数叫位宽,8 位合成一个字节。每一位都有自己的权重:最右边是 2 的 0 次方等于 1,往左依次翻倍成 2、4、8、16……把码纸上写着 1 的那些位置的权重相加,就得到这个数。比如 1011 就是 8 加 2 加 1 等于 11。

老陈心满意足地记了几天正账,可每逢遇到赊账就发愁——码纸全是正的,怎么表示“欠”?他盯着那叠码纸,灵机一动:不如拿最左边一张当“旗子”!于是,符号位的故事开始了。

0≤x=∑i=0n−1bi2i≤2n−1\displaystyle 0 \le x = \sum_{i=0}^{n-1} b_i 2^{i} \le 2^{n} - 1
位宽 n 决定了能容纳的数字总量:n 张码纸有 2 的 n 次方种排列,数值范围从 0 到 2 的 n 次方减 1。
  1. 位宽就是码纸张数n 位二进制等于 n 张码纸,每张只有 0 和 1 两种状态,所以排列总数是 2 的 n 次方,这是编码空间的全部家底。
    2n 种排列2^{n} \text{ 种排列}
  2. 权重从右往左翻倍最右位权重是 2 的 0 次方,往左依次是 2 的 1 次方、2 的 2 次方……把 1 所在位置的权重全部相加即得数值。
    x=∑i=0n−1bi2i,bi∈{0,1}\displaystyle x = \sum_{i=0}^{n-1} b_i 2^{i}, \quad b_i \in \{0, 1\}
  3. 范围两端是全集与全零全部为 0 得到最小值 0,全部为 1 得到最大值 2 的 n 次方减 1,无符号数只能记录非负账目。
    0≤x≤2n−10 \le x \le 2^{n} - 1

原码记账法:最直观的记账

老陈把最左边一张码纸当作“旗子”:旗子立着(1)表示欠账,旗子倒下(0)表示进账,剩下的码纸照旧记数字。这就是原码——人类最容易读懂的编码。+5 写成 00000101,-5 写成 10000101,一眼就能看出正负和大小。

原码的规则一句话讲完:最高位是符号位,0 代表正数,1 代表负数;其余 n 减 1 位存绝对值。于是 8 位原码能表示的范围是 -127 到 127。它读起来舒服,但算起来很痛苦:做加法要先判断两个数的符号是同号还是异号,异号还要比较绝对值大小、决定谁减谁,电路要绕一大圈。

老陈发现原码“好读不好算”:加减法要拆符号位和数值位分别处理,算盘噼里啪啦打半天。他怀念起一个古老的直觉——要是能让“减法”从账本上消失,世界就清净了。

[x]原={0 xn−2⋯x0,x≥01 xn−2⋯x0,x≤0\displaystyle [x]_{\text{原}} = \begin{cases} 0 \ x_{n-2} \cdots x_0, & x \ge 0 \\[4pt] 1 \ x_{n-2} \cdots x_0, & x \le 0 \end{cases}
原码 = 符号旗子 + 绝对值二进制。它最大的问题是 0 有了两种写法,白白浪费一个编码位置。
  1. 挂旗子定正负最高位当作符号旗子,旗子为 0 表示正数或零,旗子为 1 表示负数,其余位全部留给数值本身。
    s={0,x≥01,x<0\displaystyle s = \begin{cases} 0, & x \ge 0 \\ 1, & x < 0 \end{cases}
  2. 数值位存绝对值去掉旗子后,剩下的 n 减 1 张码纸记录 x 的绝对值的二进制,按位权相加即得数值大小。
    ∣x∣=∑i=0n−2xi2i\displaystyle |x| = \sum_{i=0}^{n-2} x_i 2^{i}
  3. 范围缩水与负零符号位占掉一位让正数范围缩水一半,而且 0 有了两种写法——00000000 和 10000000,浪费了一个编码。
    −(2n−1−1)≤x≤2n−1−1\displaystyle -(2^{n-1} - 1) \le x \le 2^{n-1} - 1

反码过渡:翻面记账法

老陈尝试了一种取巧的办法:凡是欠账,就把记录数值的码纸全部“翻面”——0 变 1,1 变 0。比如 -5 的原码是 10000101,把数值位 0000101 翻面成 1111010,得到 11111010。他管这套规则叫反码。

反码的规则是:正数和零的反码与原码完全一致;负数则保持符号位为 1,只把数值位逐位取反。数学上,负数 x 的反码等于 2 的 n 次方减 1 再加 x。反码让一些减法问题有了转机,而且它至今还在数据通信的校验和算法里发光发热——把数据按反码求和,任何一位翻转都会被检测出来。

老陈对账时发现,翻面法虽然巧妙,但两个零的问题还在,而且进位要绕一圈补回来,麻烦得很。那天晚上他看着墙上挂钟,突然跳了起来——钟表,就是答案!

[x]反={[x]原,x≥0(2n−1)+x,x<0\displaystyle [x]_{\text{反}} = \begin{cases} [x]_{\text{原}}, & x \ge 0 \\[4pt] (2^{n} - 1) + x, & x < 0 \end{cases}
反码 = 正数照抄原码,负数只翻数值位。它离“减法消失”只差一步,但“负零”依然存在。
  1. 正数照抄原码正数和零的反码与原码完全一致,不用做任何翻转,避免多此一举。
    [x]反=[x]原,x≥0[x]_{\text{反}} = [x]_{\text{原}}, \quad x \ge 0
  2. 负数数值位翻面负数反码等于原码保持符号位不动,仅把绝对值部分每一位 0 变 1、1 变 0,数学上等价于 2 的 n 次方减 1 再加 x。
    [x]反=(2n−1)+x,x<0\displaystyle [x]_{\text{反}} = (2^{n} - 1) + x, \quad x < 0
  3. 负零依旧存在全 0 表示正零,全 1 的 11111111 表示负零,两个零并存的问题依然没有解决,运算完还要处理“循环进位”。
    11111111⇒−011111111 \Rightarrow -0

补码奇迹:钟表绕圈

钟表上,3 点往回拨 3 小时是 12 点,往前拨 9 小时也是 12 点——“减 3”和“加 9”在 12 小时的钟表上是同一件事。老陈恍然大悟:如果码纸也做成一个钟表,模是 2 的 n 次方,那么减一个数就等于加上它的“补数”,减法就彻底消失了!

补码的规则因此而来:正数照旧;负数不记自己,而记 2 的 n 次方加 x 再取模后的刻度。操作口诀就是著名的“取反加一”——先对原码的数值位取反,再加 1。为什么?因为 2 的 n 次方减 x 的绝对值,等于反码再加 1。这是模运算推导出来的必然结果,不是天上掉下来的死规则。

老陈换上补码账本后,所有减法都写成加补数,算盘打得飞快。账房先生们奔走相告:这就是为什么现代计算机内部一律用补码——零唯一、减法消失、硬件极简。

[x]补=(x+2n) mod 2n,−2n−1≤x≤2n−1−1\displaystyle [x]_{\text{补}} = (x + 2^{n}) \bmod 2^{n}, \qquad -2^{n-1} \le x \le 2^{n-1} - 1
补码把减法统一成加法:任何数加上一个负数,等于加上它的补数再丢掉进位,CPU 里只需要一个加法器。
  1. 模与钟表n 位二进制只能容纳 0 到 2 的 n 次方减 1,再往前走一步就绕回 0,这个绕圈的周期就是模 M,和 12 小时钟表一个道理。
    M=2nM = 2^{n}
  2. 负数存补数负数 x 不存自己,而存 2 的 n 次方加 x,也就是从 0 往后退 x 的绝对值格所对应的那个刻度,减法在钟表上变成了加法。
    [x]补=2n+x,−2n−1≤x<0[x]_{\text{补}} = 2^{n} + x, \quad -2^{n-1} \le x < 0
  3. 取反加一的来源因为 2 的 n 次方减 x 的绝对值等于括号里 2 的 n 次方减 1 减 x 的绝对值,括号里正是反码,所以“取反加一”是推导结果而非人为规定。
    [x]补=[x]反+1[x]_{\text{补}} = [x]_{\text{反}} + 1

溢出判定:拨过十二点

钟表拨过头会怎样?3 点加 11 小时,指针停在 2 点——在模 12 的世界里这个结果没错,但如果你以为它停留在“当前半天”,就大错特错了。补码运算同理:结果超出 -128 到 127 的范围就“拨过了十二点”,这叫溢出。比如 120 加 15 等于 135,8 位补码装不下,绕圈后变成 -121,正正得负。

硬件怎么快速判断溢出?看两个进位:符号位向更外层丢出的进位 Cₙ,和最高数值位向符号位产生的进位 Cₙ₋₁。把这两个进位做异或,结果 1 就说明符号被破坏了。还要分清:无符号数不认溢出只认进位,有符号数不认进位只认溢出——一个符号位,把两种世界观彻底分开。

老陈给算盘加了一颗“报警珠”,一拨过头就叮当作响。CPU 里的 OF 标志位正是这颗报警珠——程序员靠它捕捉运算越界,编译器靠它决定要不要抛异常。

V=Cn−1⊕Cn\displaystyle V = C_{n-1} \oplus C_{n}
溢出标志 V 等于最高数值位进位与符号位进位的异或。两进位相异则溢出,相同则安然无恙。
  1. 两个关键进位Cₙ₋₁ 是最高数值位向符号位产生的进位,Cₙ 是符号位向更外层丢弃的进位,它们分别记录数值与符号的“越位”情况。
    Cn−1:进符号位,Cn:出符号位C_{n-1}: \text{进符号位}, \quad C_{n}: \text{出符号位}
  2. 异或判溢出两个进位相同说明符号正确,相异说明符号被破坏,异或结果为 1 即发生溢出。
    V=Cn−1⊕CnV = C_{n-1} \oplus C_{n}
  3. 有符号无符号分家无符号数只看 Cₙ,表示大数绕圈即进位 CF;有符号数只看 V 即溢出标志 OF,两者不可混为一谈。
    CF=Cn,OF=Cn−1⊕CnCF = C_{n}, \qquad OF = C_{n-1} \oplus C_{n}

移码与浮点:量温度的尺子

补码虽然好算,但有个小毛病:不方便比大小。-5 的补码 11111011 看着比 +5 的 00000101“大”,直接按无符号数比大小就错了。老陈想:要是有一把尺子,把刻度整体平移,让最小的数对准 0,那比较大小就变成无脑看刻度——这就是移码。

移码的定义一句话:把真值 x 加上固定偏置 2 的 n 减 1 次方。8 位移码的偏置是 128:-128 变成 00000000,0 变成 10000000,127 变成 11111111。因为平移不改变相对顺序,移码越大真值越大,硬件可以像比较正数一样比较任何数。

老陈给账房定制了一把“移码尺”,量温度、量米、量布都用同一把尺子,刻度偏移一下,比大小就成了看数字。计算机的浮点数、排序引擎、GPU 的数值比较流水线,都在悄悄用这把尺子。

[x]移=x+2n−1,−2n−1≤x≤2n−1−1\displaystyle [x]_{\text{移}} = x + 2^{n-1}, \qquad -2^{n-1} \le x \le 2^{n-1} - 1
移码 = 真值加偏置。它把最小负数映射成全零,让“谁大谁小”变成无符号整数的直接比较。
  1. 整体平移偏置把真值 x 加上固定偏置 2 的 n 减 1 次方,相当于把尺子的零刻度往右挪了半程,最小负数变成全零编码。
    [x]移=x+2n−1[x]_{\text{移}} = x + 2^{n-1}
  2. 大小顺序一致平移不改变相对顺序,移码越大真值越大,硬件像比较无符号数一样比较移码即可。
    x1<x2  ⟺  [x1]移<[x2]移\displaystyle x_1 < x_2 \iff [x_1]_{\text{移}} < [x_2]_{\text{移}}
  3. 与补码只差符号位移码与补码的数值位完全相同,只要把补码符号位取反就是移码,电路上用一条异或门就能互转。
    [x]移=[x]补⊕2n−1[x]_{\text{移}} = [x]_{\text{补}} \oplus 2^{n-1}

编码互转:一本账四种写法

同一个 -5,原码 10000101、反码 11111010、补码 11111011、移码 01111011——四种写法,本质都是同一个“欠五两”。账房先生们养成了习惯:给人看的账本用原码,算总账用补码,浮点账册用移码,反码则藏在校验和里当“哨兵”。

互转法则其实很简单:原码取反(符号位不动)得反码,反码加 1 得补码,补码符号位取反得移码。反过来也一样:补码取反加 1 还原绝对值,添上符号就是原码。还有两个重要的工程细节:符号扩展——4 位补码 1101 扩展到 8 位是 11111101,高位一律补符号位,值不变;真值还原——符号位为 1 时,把补码当作无符号数读数再减去 2 的 n 次方,就得到真正的负数。

老陈把四种账法抄成口诀贴在账房墙上:“原码看、补码算、移码比、反码桥。”计算机层次也是如此:人读编译器的符号表,硬件算补码,浮点比移码——四种编码各司其职,撑起整个数字世界。

x={[x]补,符号位=0[x]补−2n,符号位=1\displaystyle x = \begin{cases} [x]_{\text{补}}, & \text{符号位} = 0 \\[4pt] [x]_{\text{补}} - 2^{n}, & \text{符号位} = 1 \end{cases}
补码还原真值:符号位 0 直接读,符号位 1 时无符号读数减 2 的 n 次方。这是反汇编、调试器打印负数时每天都在做的事。
  1. 补码还原真值符号位为 0 时直接读数;为 1 时把无符号读数减去模 2 的 n 次方,就得到负的真值。
    x={[x]补,符号位=0[x]补−2n,符号位=1\displaystyle x = \begin{cases} [x]_{\text{补}}, & \text{符号位} = 0 \\ [x]_{\text{补}} - 2^{n}, & \text{符号位} = 1 \end{cases}
  2. 取反加一互为逆运算对负数补码再取反加一得到的是绝对值,添上符号即原码,一组互逆操作让转换可用一条指令完成。
    [−x]补=[x]补‾+1[-x]_{\text{补}} = \overline{[x]_{\text{补}}} + 1
  3. 符号扩展短补码扩到长补码,高位一律补符号位,数值大小不变,这是汇编语言与类型提升的底层依据。
    1101(4位)⇒11111101(8位)1101_{(4\text{位})} \Rightarrow 11111101_{(8\text{位})}

AI 应用场景

嵌入式传感器温度解码

阶段:数据预处理与特征工程的数据解析

解决的问题:AIoT 设备里 I2C 温湿度传感器返回的是 16 位补码数据,新手常按无符号数解读,35 度的室温变成 65501 的天文数字。理解补码后,一句“读数减 65536”就能还原真实温度,避免脏数据喂给模型。

具体案例:某智慧农业项目用 STM32 采集大棚温度,传感器原始值 0xFF38(补码),正确解析为 -200,即 -20.0 度;若误读为 65336 度,温度异常检测模型会立刻误报。

网络传输中的数据完整性校验

阶段:分布式训练与联邦学习中的通信传输

解决的问题:分布式训练需要频繁同步梯度,网络丢包或翻转一位会让整个训练崩溃。TCP/IP 校验和至今采用“反码求和”:接收方把数据按 16 位反码相加,结果全 1 才算完整。这是反码在工程中活着的证明。

具体案例:某大模型训练集群中,节点间梯度同步包被网卡静默丢弃,工程师用反码校验和定位到故障交换机,避免了模型参数静默损坏。

浮点数排序与比较的底层加速

阶段:推理引擎中的算子优化与张量排序

解决的问题:AI 推理时经常要对浮点置信度排序、做非极大值抑制,若逐位解析 IEEE 754 浮点格式会很慢。移码让阶码可按无符号整数直接比较,GPU 与 CPU 的浮点比较流水线因此省掉大量分支判断。

具体案例:某图像识别引擎做 NMS(非极大值抑制)时,把 float 的阶码段按移码读取,排序速度提升约 30%,端到端推理延迟明显下降。

Python 演示

python53 行
def to_bin(x, n):    """把非负整数 x 补成 n 位二进制字符串"""    return bin(x)[2:].zfill(n)  def sign_magnitude(x, n=8):    """原码: 符号位 + 绝对值二进制"""    if x < -(2 ** (n - 1) - 1) or x > 2 ** (n - 1) - 1:        raise ValueError("原码无法表示该数")    sign = "1" if x < 0 else "0"    return sign + to_bin(abs(x), n - 1)  def ones_complement(x, n=8):    """反码: 负数对原码的数值位按位取反"""    s = sign_magnitude(x, n)    if x >= 0:        return s    body = "".join("1" if b == "0" else "0" for b in s[1:])    return "1" + body  def twos_complement(x, n=8):    """补码: 负数等于模 2^n 加 x(取反加一的数学本质)"""    if x < -(2 ** (n - 1)) or x > 2 ** (n - 1) - 1:        raise ValueError("补码无法表示该数")    if x >= 0:        return to_bin(x, n)    return to_bin((1 << n) + x, n)  def biased(x, n=8):    """移码: 真值整体右移 2^(n-1),偏置为 2^(n-1)"""    return to_bin(x + 2 ** (n - 1), n)  def add8(a, b):    """模拟 8 位补码加法,返回有符号结果(含溢出)"""    r = (a + b) % (1 << 8)    return r - (1 << 8) if r >= (1 << 7) else r  def main():    for v in (5, -5, 127):        print("{:>4} 原码 {} 反码 {} 补码 {} 移码 {}".format(            v, sign_magnitude(v), ones_complement(v),            twos_complement(v), biased(v)))    print("-5 补码等于 256-5:", twos_complement(-5) == to_bin(256 - 5))    print("add8(120, 15) =", add8(120, 15), "  120+15 已越界,溢出为 -121")  if __name__ == "__main__":    main()
目的:用 Python 实现四种编码的位串转换,并模拟 8 位补码加法溢出,直观验证“取反加一”与“钟表模运算”的本质。
  1. return sign + to_bin(abs(x), n - 1)原码 = 符号位拼上绝对值二进制,符号位只是最前面的一面“旗子”。
  2. body = "".join("1" if b == "0" else "0" for b in s[1:])只翻数值位、保持符号位,这正是反码与“整个数取反”的区别。
  3. return to_bin((1 << n) + x, n)负数补码 = 模 2^n 加负数,一句“钟表绕圈”的代码实现。
  4. return to_bin(x + 2 ** (n - 1), n)移码把数轴整体右移 2^(n-1),让最小负数变成全 0。
  5. r = (a + b) % (1 << 8)模拟 8 位加法器的“绕回 0”行为,超出 255 的部分被模掉。
  6. return r - (1 << 8) if r >= (1 << 7) else r按符号数解读:读到 135 时减去 256,还原出 -121,露出溢出本质。

常见误区

误区反码就是把所有位都翻过来,符号位也跟着翻!-5 的反码应该是 01111010 吧?

正解反码取反只针对数值位,符号位是“旗子”,必须保持不动。旗子代表“欠账”,翻旗子等于把欠账翻成收账,账本全乱。正确做法:-5 原码 10000101,数值位 0000101 翻成 1111010,得到 11111010。想象你只需要把数字卡片翻面,但前面那块“正负牌照”不许动。

为何常见:因为“按位取反”四个字听上去像全部位,加上很多资料只写“取反”两个字,没强调符号位除外,初学者第一反应就是把 8 位全翻。

误区补码为什么是反码加一?这是前人定下的规矩,我只能背下来,考试时经常记混。

正解补码的根源是模运算。n 位数的模是 2 的 n 次方,-x 的补码就是 2 的 n 次方减 x。而 2 的 n 次方减 x 等于 (2 的 n 次方减 1 减 x) 再加 1,括号里恰好是反码,所以“取反加一”是推导出来的结论。就像钟表上往回拨 3 小时等于往前拨 9 小时,理解了钟表就不用背。

为何常见:教材通常直接给规则再举例子,很少先讲模与钟表,学生记住了操作,没理解为什么,换个位数就发慌。

误区加法算出来最高位有进位,说明结果溢出了,要报错。

正解进位是最高位向更外层丢出去的 1,属于模运算里正常的“绕回 0”,无符号加法甚至期待这个进位。溢出是结果超出了有符号数的表示范围,判断依据是符号位进位 Cₙ₋₁ 和外层进位 Cₙ 是否相异。120 加 15 得 135,丢掉进位后读作 -121,此时最高位进位可能是 1,但真正报警的是 OF 标志。

为何常见:小学竖式里“满十进一”的观念根深蒂固,误把模运算的进位当成错误信号,混淆了 CF 与 OF 两个标志。

误区移码和补码差不多,就是多了一个偏置值,平时编程又用不到,了解就行。

正解移码把整个值域平移,让最小负数变成全 0,于是移码之间的大小排序与无符号整数完全一致。浮点数 IEEE 754 的阶码正是用移码存储,比较两个浮点数大小时硬件只需按无符号整数比较阶码即可。没有移码,浮点比较要拆符号位、阶码、尾数三部分,速度和电路复杂度都不可接受。

为何常见:高级编程语言里看不到移码,它藏在硬件和标准里,学生觉得冷门,忽略了它在浮点体系中的核心地位。

误区原码读起来最方便,符号一眼看清,为什么 CPU 不直接用原码做加减法?

正解原码做加减法要分同号异号讨论,还要比较绝对值大小、决定谁减谁,控制电路复杂且慢。补码把减法统一成加法,任何加减法都变成同一个二进制加法运算,硬件只需要一个加法器加一个取反电路,速度最快、成本最低。人的阅读习惯与机器的计算效率是两回事,所以 CPU 内部只用补码。

为何常见:从“人读起来方便”的直觉出发,忽略了硬件最怕分支判断,过度关注“读”而忘了“算”才是机器的本职。

面试题

Q1. 8 位二进制中,-5 的原码表示为?

单选题 · easy

  • A.10000101← 正确答案
  • B.11111010
  • C.11111011
  • D.10000001

答案:A。原码 = 符号旗子 1 + 绝对值 5 的 7 位二进制 0000101,拼接得 10000101。B 是反码,C 是补码,D 是 -1 的原码,都是精心设计的混淆项。

Q2. 8 位补码能表示的范围是?

单选题 · easy

  • A.-128至127← 正确答案
  • B.-127至128
  • C.-255至255
  • D.-256至255

答案:A。n 位补码范围是 -2 的 n-1 次方到 2 的 n-1 次方减 1,代入 n=8 得 -128 到 127。原码和反码只能到 ±127,多出来的 -128 正是补码“零唯一化”省出的编码位置。

Q3. 5 位补码 11011 对应的十进制真值是?

单选题 · mid

  • A.真值等于 -5← 正确答案
  • B.真值等于 -11
  • C.真值等于 -27
  • D.真值等于 27

答案:A。11011 的无符号读数 27,符号位为 1,真值 = 27 - 2 的 5 次方 = 27 - 32 = -5。也可以用“取反加一”验证:11011 取反得 00100,加一得 00101 等于 5,所以是 -5。

Q4. 原码与反码共同的缺憾是?

单选题 · easy

  • A.零有两种表示← 正确答案
  • B.无法表示负数
  • C.无法表示正数
  • D.运算速度太慢

答案:A。原码的 00000000 与 10000000 分别表示 +0 和 -0;反码的 11111111 也表示 -0。两个零并存既浪费编码又让比较逻辑多一堆分支,直到补码才把零唯一化成全 0。

Q5. 关于补码,下列说法正确的有?

多选题 · mid

  • A.负数补码等于反码加一← 正确答案
  • B.补码可统一加减法运算← 正确答案
  • C.负零在补码里也存在
  • D.8位补码能表示负128至127← 正确答案

答案:ABD。负数补码 = 反码 + 1;补码用模运算把减法变成加法,CPU 只需一个加法器;补码范围是 -128 到 127。C 错误,补码中零只有 00000000 一种表示,这正是它的核心优势。

Q6. 8 位补码运算 120 + 15,按有符号数解释结果是多少?

单选题 · mid

  • A.结果等于 -121← 正确答案
  • B.结果等于 135
  • C.结果等于 121
  • D.结果等于 -135

答案:A。120 + 15 = 135,超出 8 位补码上限 127,发生溢出。模 256 绕圈后 135 - 256 = -121,表现为“正正得负”。硬件 OF 标志位会置 1,提醒程序员结果不可信。

Q7. 以下哪些 8 位补码运算必然发生有符号溢出?

多选题 · hard

  • A.127 加 1← 正确答案
  • B.64 加 64← 正确答案
  • C.100 加 20
  • D.127 减 1

答案:AB。127+1=128 和 64+64=128 都越过 8 位补码上限 127,必然溢出;100+20=120 和 127-1=126 都在 -128 到 127 范围内,结果正确。判断溢出看符号位进位与外层进位是否相异,而不是看有没有进位。

Q8. 浮点数阶码采用移码的根本原因是?

单选题 · mid

  • A.大小顺序与无符号数相同← 正确答案
  • B.移码表示范围最大
  • C.移码运算速度最快
  • D.移码能直接表示小数

答案:A。移码对真值整体平移,不改变大小顺序,于是阶码可以当作无符号整数直接比较,比浮点比较快得多。移码范围并不比其他码制大,速度优势来自“免去符号判断”,与表示小数无关。

Q9. 8 位编码 10000000 在不同码制下的解读,正确的有?

多选题 · hard

  • A.按原码读数是负零← 正确答案
  • B.按反码读数是负127← 正确答案
  • C.按补码读数是负128← 正确答案
  • D.按移码读数是负128

答案:ABC。原码 10000000 = 符号 1 + 绝对值 0,即 -0;反码 10000000 对应原码 11111111,即 -127;补码 10000000 = -2 的 7 次方 = -128。移码 10000000 换算真值 = 128 - 128 = 0,D 错误。

Q10. 4 位补码 1101 扩展为 8 位补码,正确的是?

单选题 · hard

  • A.10001101
  • B.11111101← 正确答案
  • C.10000101
  • D.00001101

答案:B。符号扩展规则是高位一律补符号位。1101 的符号位为 1,所以扩展后高 4 位全补 1,得 11111101,数值不变仍为 -3。A 和 C 把符号位扩展成 1 但中间夹了 0,等于篡改了数值,D 把负数扩展成正数,都是典型错误。