信息表示基础:进制、编码与补码
组成原理里最容易被低估的一件事,就是“表示”本身。
很多时候我们更容易把注意力放在运算上:加法怎么做、CPU 怎么执行、程序怎么跑。但在真正开始运算之前,计算机必须先回答一个更基础的问题:
现实世界里的数字、字符、状态和指令,到底要用什么方式存进机器里?
如果这个问题没有解决,后面的计算根本无从谈起。也正因为如此,进制、编码和补码看起来像是入门知识,实际上却是整个计算机系统能否稳定工作的基础。
一、为什么计算机使用二进制
从直觉上说,人类更习惯十进制,为什么计算机偏偏选择二进制?
根本原因不是数学上二进制更“高级”,而是硬件实现更自然。
在电子电路里,更容易稳定地区分的是两种状态:
- 高电平和低电平;
- 通电和断电;
- 真和假;
- 1 和 0。
如果用十种状态去表示 0 到 9,不但电路更复杂,抗干扰和判定难度也会明显上升。二进制只需要区分两种状态,硬件实现更简单、稳定性更高。
所以计算机选择二进制,本质上是工程上的最优折中,而不是为了“故意让人难学”。
二、进制到底在描述什么
进制的本质,是一种位置计数规则。
以十进制为例,325 表示的是:
3 × 10²2 × 10¹5 × 10⁰
而二进制 1011 表示的是:
1 × 2³0 × 2²1 × 2¹1 × 2⁰
也就是说,不同进制的区别不在于“数字长得像不像”,而在于每一位的权值不同。
理解这一点之后,二进制、八进制、十六进制之间的转换就更容易看成同一类问题:
都是在不同权值体系下表达同一个数。
三、为什么经常还会看到八进制和十六进制
虽然机器底层是二进制,但程序员日常并不总是直接写一长串 0 和 1。原因很简单:太难读。
于是工程上常用两种更方便的缩写形式:
- 八进制:每 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. 忽略表示范围
机器整数是有边界的,溢出并不是意外,而是固定宽度表示的必然结果。
总结
信息表示是组成原理最底层、也最关键的一块基础。真正值得先抓住的,是这些认识:
- 计算机使用二进制,首先是因为硬件上更容易稳定实现;
- 不同进制本质上只是同一数值在不同权值体系下的表达;
- 计算机处理的不是“自然含义”,而是编码后的位模式;
- 补码之所以重要,是因为它统一了有符号整数的运算规则;
- 字符、数值、指令本质上都属于“信息如何编码表示”的问题。
把这一部分理解透之后,后面再去看浮点数、逻辑电路和指令系统,会更容易看清:计算机的一切处理,最开始都建立在“如何表示信息”这一步上。
参考资源:
- 《深入理解计算机系统》
- 《编码:隐匿在计算机软硬件背后的语言》
- Wikipedia - Two’s complement
- Unicode 官方文档