信息表示基础:进制、编码与补码

组成原理里最容易被低估的一件事,就是“表示”本身。

很多时候我们更容易把注意力放在运算上:加法怎么做、CPU 怎么执行、程序怎么跑。但在真正开始运算之前,计算机必须先回答一个更基础的问题:

现实世界里的数字、字符、状态和指令,到底要用什么方式存进机器里?

如果这个问题没有解决,后面的计算根本无从谈起。也正因为如此,进制、编码和补码看起来像是入门知识,实际上却是整个计算机系统能否稳定工作的基础。

一、为什么计算机使用二进制

从直觉上说,人类更习惯十进制,为什么计算机偏偏选择二进制?

根本原因不是数学上二进制更“高级”,而是硬件实现更自然。

在电子电路里,更容易稳定地区分的是两种状态:

  • 高电平和低电平;
  • 通电和断电;
  • 真和假;
  • 1 和 0。

如果用十种状态去表示 0 到 9,不但电路更复杂,抗干扰和判定难度也会明显上升。二进制只需要区分两种状态,硬件实现更简单、稳定性更高。

所以计算机选择二进制,本质上是工程上的最优折中,而不是为了“故意让人难学”。

二、进制到底在描述什么

进制的本质,是一种位置计数规则。

以十进制为例,325 表示的是:

  • 3 × 10²
  • 2 × 10¹
  • 5 × 10⁰

而二进制 1011 表示的是:

  • 1 × 2³
  • 0 × 2²
  • 1 × 2¹
  • 1 × 2⁰

也就是说,不同进制的区别不在于“数字长得像不像”,而在于每一位的权值不同。

理解这一点之后,二进制、八进制、十六进制之间的转换就更容易看成同一类问题:

都是在不同权值体系下表达同一个数。

三、为什么经常还会看到八进制和十六进制

虽然机器底层是二进制,但程序员日常并不总是直接写一长串 01。原因很简单:太难读。

于是工程上常用两种更方便的缩写形式:

  • 八进制:每 3 位二进制对应 1 位八进制;
  • 十六进制:每 4 位二进制对应 1 位十六进制。

例如:

  • 二进制 1111 对应十六进制 F
  • 二进制 1010 1100 可以写成十六进制 AC

这在看内存、调试寄存器、分析机器码、抓包或看协议字段时尤其常见。所以八进制和十六进制不是另一套系统,而是二进制的“更适合人类阅读的表示形式”。

四、数值在机器里不是“直接存数字”,而是存编码

这一步很关键。

计算机不会理解“这是负数”“这是字符 A”“这是指令 add”这样的自然语义。它看到的只是一个个比特位组合。

所以任何信息进入机器之前,都必须先经过编码。

例如:

  • 十进制整数要编码成二进制位模式;
  • 字符要编码成 ASCII、Unicode、UTF-8 等形式;
  • 指令要编码成特定的机器码格式;
  • 图像、音频、视频也都有自己的编码规则。

因此“表示”这个问题,本质上就是:

用一组位模式去约定性地表达现实世界中的某种含义。

五、无符号整数为什么容易理解

整数表示里,最简单的是无符号数。

如果用 n 位二进制表示无符号整数,那么每一位都只看作权值位,没有“正负”之分。

例如 4 位无符号数可表示:

  • 最小值:0000,即 0;
  • 最大值:1111,即 15。

所以它的范围一般是:

1
0 ~ 2ⁿ - 1

无符号数很好理解,但它有明显局限:不能表示负数。

六、为什么负数表示会变得麻烦

如果计算机只存正数,那很多运算都做不了。例如温度、余额变化、坐标偏移、数组差值、减法结果,都可能出现负数。

那负数应该怎么表示?

最直觉的想法是“加一个符号位”,也就是:

  • 最高位表示正负;
  • 剩下的位表示大小。

这就是原码思路。

例如在 8 位里:

  • 0000 0101 表示 +5
  • 1000 0101 表示 -5

看起来很自然,但它的问题很快就暴露出来了:

  • 正负零会出现两种表示;
  • 加减法电路实现不统一;
  • 运算规则会变复杂。

所以原码直观,但不适合作为机器里统一的整数运算表示方式。

七、补码为什么会成为主流表示

补码最核心的价值,不是“更好看”,而是:

它让加法器同时也能自然处理减法和负数运算。

在补码体系下:

  • 正数的补码和原码一致;
  • 负数的补码等于“对应正数按位取反再加一”。

例如在 8 位里,+5 是:

1
0000 0101

那么 -5 的补码是:

1
1111 1011

这样设计的好处是,硬件在做整数加法时,不需要专门区分“这是加法还是减法”。很多情况下统一用加法电路就能完成。

这大大简化了运算器设计,也解释了为什么现代计算机普遍使用补码表示有符号整数。

八、怎么理解补码的直觉

如果只背“取反加一”,补码很容易记混。更好的理解方式,是把它看成模运算体系中的一种表示。

以 8 位整数为例,总共有 2⁸ = 256 种位模式。可以把它理解成一个长度为 256 的环。

  • 0000 0000 表示 0;
  • 往前走到 0111 1111 是 127;
  • 再继续往前就进入负数区;
  • 1111 1111 表示 -1;
  • 1111 1011 表示 -5。

这样一来,“加一个负数”其实就变成了在这个环上做统一的加法。于是硬件实现会简单很多。

九、补码能带来哪些直接结果

1. 最高位天然参与数值表示

在补码中,最高位不只是“符号标记”,它本身也参与整个数值体系。

2. 零只有一种表示

不会再出现原码里的 +0-0 两套写法。

3. 加减法规则更统一

对运算器来说,这一点最重要。统一规则意味着硬件更简洁,成本更低。

4. 表示范围会不对称

n 位补码整数的范围通常是:

1
-2ⁿ⁻¹ ~ 2ⁿ⁻¹ - 1

例如 8 位补码范围是:

1
-128 ~ 127

这说明负数一侧会比正数多一个值。

十、溢出为什么会发生

补码体系不是无限大的,它只能表示固定范围内的数。

如果运算结果超出可表示范围,就会发生溢出。

例如在 8 位补码里:

  • 最大正数是 127;
  • 如果再加 1,结果就无法用 8 位有符号整数正常表示。

这件事在工程里并不只是课堂例题,它会影响:

  • C/C++ 中整数溢出的行为;
  • 哈希、计数器、时间戳处理;
  • 金额、库存、偏移计算;
  • 安全漏洞中的边界判断错误。

所以理解补码,不只是为了会做题,更是为了知道机器整数并不是“数学上的无限整数”。

十一、字符编码为什么和数值编码一样重要

除了整数,字符也是最常见的信息类型。

计算机并不认识“字母 A”“汉字中”,它只认识对应的编码值。

常见字符编码包括:

  • ASCII:最经典的英文字符编码;
  • Unicode:统一字符集标准;
  • UTF-8:最常见的变长编码实现。

这也说明一个更大的事实:

计算机里所有信息,本质上最终都要落成位模式;差别只在于这些位模式被赋予了什么解释规则。

十二、学习信息表示时最容易踩的坑

1. 把“位模式”和“解释方式”混为一谈

同一串二进制位,可以被解释成无符号整数、有符号整数、字符编码、颜色值甚至指令片段。关键不是位本身,而是解释规则。

2. 只会背补码公式,不理解为什么这样设计

真正重要的是明白:补码的核心价值在于让整数运算硬件更统一。

3. 忽略表示范围

机器整数是有边界的,溢出并不是意外,而是固定宽度表示的必然结果。

总结

信息表示是组成原理最底层、也最关键的一块基础。真正值得先抓住的,是这些认识:

  • 计算机使用二进制,首先是因为硬件上更容易稳定实现;
  • 不同进制本质上只是同一数值在不同权值体系下的表达;
  • 计算机处理的不是“自然含义”,而是编码后的位模式;
  • 补码之所以重要,是因为它统一了有符号整数的运算规则;
  • 字符、数值、指令本质上都属于“信息如何编码表示”的问题。

把这一部分理解透之后,后面再去看浮点数、逻辑电路和指令系统,会更容易看清:计算机的一切处理,最开始都建立在“如何表示信息”这一步上。

参考资源