第 2 章 · 数据的表示与运算

整数用有限位表示(原码/反码/补码),实数用二进制逼近(IEEE 754)——决定第 3 章 ALU 与 C 的 unsigned/浮点陷阱。

2.1 进制与位权

一切进制都是位权展开:任意 R 进制数 d_n…d_1d_0 的值 = Σ d_i × R^i

  • 二进制 → 十六进制:4 位一组(16=2⁴),1010 1110 = 0xAE——一字节正好两个十六进制位。
  • 十进制 → 二进制:整数「除 2 取余,逆序」;小数「乘 2 取整,顺序」。
  • 小数二进制陷阱0.1 转二进制是无限循环小数 0.0001100110011…,计算机里无法精确表示——浮点误差源头(见 2.4)。

注意:二进制表示十进制小数不保证精确;但二进制↔十六进制是 2 的幂次换算,纯位分组、无损无舍入。

2.2 原码、反码、补码

负数三种表示法(8 位 -5):

表示-5 的编码正数区零的表示关键性质
原码1000 0101最高位是符号0000 00001000 0000 两个零人读直观,但加减法要判符号,硬件复杂
反码1111 1010按位取反两个零减法仍要处理「循环进位」,未根本解决
补码1111 1011反码+1只有一个零减法=加法,硬件只需加法器

补码是唯一正确解

  1. 唯一零:原码/反码有 +0/-0 两零,浪费编码且比较要特判;补码零唯一,1000 0000 解放为 -128,8 位范围 -128~+127
  2. 减法变加法:补码本质是模运算(模 2ⁿ),-x ≡ 2ⁿ-x (mod 2ⁿ)A-B = A+(2ⁿ-B) 溢出位自然丢弃,硬件无需减法器。
  3. 符号位参与运算:补码最高位直接当数值位一起算,符号自动正确;原码/反码须单独处理符号。

对比:-x 补码 = 取反加一(反向 -(-x)=x 也成立)。机器捷径:从右往左找第一个 1,其左全取反、其右不动——-5 原码 0000 0101 → 左 0000 010 取反 1111 101,拼右 1 = 1111 1011

求 -5 的补码(8 位):
  +5 = 0000 0101
  取反 = 1111 1010
  加一 = 1111 1011   ← -5

2.3 定点数与溢出判定

定点数:小数点位置固定(整数是小数点在最右端的特例)。溢出必须检测,两条等价判据:

  1. 进位判据:符号位进位 C_out 与次高位进位 C_in 不同即溢出,V = C_out ⊕ C_in
  2. 双符号位判据:两位符号位参与运算,结果两位不同(01/10)即溢出。
8 位补码加法溢出示例:
  +100  (0110 0100)
+ +50   (0011 0010)
= +150  (1001 0110)  ← 符号位变成 1,结果却是 +150 > 127,溢出!

注意:C 的 int 溢出是未定义行为(UB),编译器可做任何事;unsigned 溢出是明确定义的模 2ⁿ 回绕。别把「补码回绕」当「int 安全溢出」。

2.4 浮点数:IEEE 754

实数用科学计数法逼近:(-1)^s × 1.fraction × 2^(exponent - bias)

精度符号 s指数 e尾数 fractionbias范围量级
float1 位8 位23 位127~10⁻³⁸ ~ 10³⁸
double1 位11 位52 位1023~10⁻³⁰⁸ ~ 10³⁰⁸
  • 规格化与隐藏位:规格化数尾数第一位恒为 1,不存,白赚 1 位精度。指数存偏置值(真值+bias),可按无符号整数直接比大小。
  • 特殊值:指数全 0+尾数全 0 = ±0;指数全 1+尾数全 0 = ±∞;指数全 1+尾数非 0 = NaN0.0/-0.0 编码不同但比较相等。
  • 非规格化数(denormal):指数全 0、尾数非 0 时隐藏位变 0,表示比最小规格化数更接近 0 的数——填平「突然下溢到 0」(gradual underflow)。
  • 舍入:超出尾数精度的位按最近舍入(ties-to-even)丢弃。2.1 的 0.1 即被舍入成最近近似值。
0.1 (double) 的实际存储值 ≈ 0.1000000000000000055511151231257827...
所以 0.1 + 0.2 != 0.3  (结果是 0.30000000000000004)

对比:定点小数位固定,范围小但精度均匀、运算快(纯整数电路,第 3 章);浮点用指数换巨大动态范围,但精度不均匀(数越大间隔越疏,有效位固定 24/53 位)。金融/计时等需精确十进制且无舍入的场景,用定点或十进制库,别用二进制浮点。

2.5 与运算器 / C 语言对照

  • 第 3 章:补码让 ALU 只靠加法器做减法;溢出标志 OF 正是 2.3 的 C_out ⊕ C_in。浮点加减 = 对阶→尾数运算→规格化→舍入四步,比整数加法慢得多。
  • Cfloat/double 对应 IEEE 754,int/unsigned 对应补码。(int)0.1 截断、浮点比较不能用 ==1/0.0inf0.0/0.0NaN,根源都在本章。
  • Javaint 溢出是明确定义的补码回绕(非 UB);double 完全遵循 IEEE 754。
  • Pythonint 是任意精度大整数(数组存位)不溢出、代价是慢;float 仍是 IEEE 754 double,0.1+0.2 问题照旧。