引言:掰手指的艺术

小时候学数数,我们从 1 数到 10、到 100,用的是十进制。你有没有想过:为什么偏偏是 10,而不是 7、不是 12?

答案其实非常朴素——因为人有十根手指。十进制从来不是宇宙的真理,它只是”数数”这个动作的历史惯性。

你或许会疑惑,为什么计算机会使用二进制作为基本运算和存储单位,这其实和电路的特性有关,电路只能表示精确的表示两种状态:开(高电平)和关(低电平)。而二进制正好能表示这两种状态,因此二进制成为了计算机的母语。

人用十进制思考,机器用二进制存储,中间就需要一座桥。这座桥就是八进制十六进制——它们不是第三种、第四种语言,而是二进制的”速记符号”。


一、进制是什么:一切皆”逢几进一”

先看一个你早就烂熟于心的事实:数字 365 里的每个数字,重量并不相同。

365 = 3 × 10² + 6 × 10¹ + 5 × 10⁰
    = 300    + 60    + 5

最右边的 5 是”个位”(10⁰),往左依次是十位(10¹)、百位(10²)……每一位的值 = 该位数字 × 进制的位权,而位权就是”进制”的若干次方。十进制满 10 进 1,所以叫”逢十进一”。

二进制完全一样,只是把 10 换成 2,逢二进一

1101 = 1 × 2³ + 1 × 2² + 0 × 2¹ + 1 × 2⁰
     = 8      + 4      + 0      + 1
     = 13

八进制逢八进一,十六进制逢十六进一。把它们统一写成数学通式:

任意进制数的值 = Σ(每一位数字 × 该进制的位权)

一句话:进制只是数字的”写法”不同,数值从来没有变过。 十进制的 13、二进制的 1101、八进制的 15、十六进制的 0xD,是同一个数穿了四件不同的衣服。


二、根本大法:二进制与十进制的互转

2.1 十进制 → 二进制:除 2 取余

把 173 转成二进制,记住口诀:除 2 取余,倒着读

173 ÷ 2 = 86 余 1  ← 最低位
 86 ÷ 2 = 43 余 0
 43 ÷ 2 = 21 余 1
 21 ÷ 2 = 10 余 1
 10 ÷ 2 =  5 余 0
  5 ÷ 2 =  2 余 1
  2 ÷ 2 =  1 余 0
  1 ÷ 2 =  0 余 1  ← 最高位

从下往上读:10101101。验证一下:128 + 32 + 8 + 4 + 1 = 173 ✓。

原理很简单:每次除以 2 得到的余数,就是当前最低位的二进制值;剩下的商继续”剥”下一位,剥到 0 为止。除 8、除 16 同理,只是把 2 换成 8、16。

2.2 二进制 → 十进制:按权展开

反过来就更快了——记住一张 2 的幂表,直接心算:

2^n2^n
2⁰12⁶64
22⁷128
42⁸256
82⁹512
2⁴162¹⁰1024
2⁵322²⁰1048576

比如 110101,从高位往低位念:32 + 16 + 4 + 1 = 53。看到哪个位是 1,就把对应权值加起来,0 直接跳过。练几次之后,8 位以内的二进制你可以在两秒内转成十进制。

TIP

这个方法也能用于十进制转二进制

如果你需要转成二进制的十进制数字不是很大,你可以尝试用 2^n 的幂表中的值来进行组合使其加起来等于目标数字。然后,把每个组合的权值写成二进制,就得到了目标数字的二进制表示。

例如:520 = 512 + 8 = 1000001000

2.3 小数怎么办:乘 2 取整

小数部分换一套口诀:乘 2 取整,顺着读

把 0.625 转成二进制:

0.625 × 2 = 1.25  → 取 1,剩 0.25
0.25  × 2 = 0.5   → 取 0,剩 0.5
0.5   × 2 = 1.0   → 取 1,剩 0

顺着读:0.101。验证:1/2 + 1/8 = 0.625 ✓。

于是 173.625 = 10101101.101。注意:并非所有十进制小数都能精确转成二进制。比如 0.1 转二进制会得到无限循环的 0.000110011001100...——这里就涉及到了”浮点数”和“精度”的相关概念,这里不再展开。


三、八进制与十六进制:二进制的”缩写”

二进制忠实,但啰嗦。一个 32 位的整数,写成二进制要 32 个字符,谁也记不住。

聪明的古人(程序员)发现:8 = 2³,16 = 2⁴。这意味着:

  • 3 位二进制 = 1 位八进制
  • 4 位二进制 = 1 位十六进制

于是二进制可以被”分团压缩”成短得多的形式。十六进制特别受欢迎,因为它把 1 个字节(8 位)正好压成 2 个字符。先记住十六进制的字母表:

十进制二进制十六进制
000000
100011
200102
300113
401004
501015
601106
701117
810008
910019
101010A
111011B
121100C
131101D
141110E
151111F

这张表值得背到条件反射。F1111(4 个 1),A1010C1100——多看一眼,它们就会成为你的老朋友。


四、快速互转的黄金法则

4.1 二进制 ↔ 十六进制:四位一组

以小数点为界,整数部分向左、小数部分向右,每 4 位一组(不足补 0),每组直接对照上表。

拿刚才的 10101101.101 练手:

1010 1101 . 1010      ← 小数部分补 0 凑满 4 位
  A    D   .   A

结果:0xAD.A = 173.625 ✓。这就是 C 语言里 0x 前缀的由来——十六进制是给二进制发明的速记本。

4.2 二进制 ↔ 八进制:三位一组

同样的套路,只是把”4 位”换成”3 位”:

10 101 101 . 101      ← 整数部分左侧补 0
 2   5   5  .  5

结果:255.5(八进制)= 173.625 ✓。C 语言里以 0 开头的整数是八进制,比如 0755,见下文第六节。

4.3 八进制 ↔ 十六进制:走二进制这座桥

八进制和十六进制之间没有直接对应关系,但谁都能转二进制,所以桥接一下即可:

0xAD  →  1010 1101  →  10 101 101  →  0255(八进制)

整个过程 5 秒,比任何”直接公式”都可靠。

4.4 十进制 ↔ 八/十六进制:除 8 取余 / 除 16 取余

跟除 2 取余一模一样,只是除数换成 8 或 16(注意余数 1015 要写成 AF):

173 ÷ 16 = 10 余 13(D)
 10 ÷ 16 =  0 余 10(A)

从下往上读:0xAD。其实更快的路径是:先转二进制,再四位分组——反正你总得会二进制。

一张图总结全部路径:

        除 2 取余             三位一组              走二进制桥
十进制 ←————————→ 二进制 ←————————→ 八进制 ←————————→ 十六进制
        按权展开             四位一组
                            ←————————→

所有进制互转,最终都归结为”除基取余 + 分组对照”两招。


五、心算技巧与常用记忆表

5.1 背下这些”锚点”

  • 0xFF = 255:1 个字节的最大值
  • 0xFFFF = 65535:2 个字节(1 个 short)的最大值
  • 0x7FFFFFFF = 2147483647INT_MAX,32 位有符号整数上限
  • 0x80000000:32 位补码的 INT_MIN(-2147483648)
  • 0xFFFFFFFF:32 位无符号最大值,等价于有符号的 -1
  • 0x40000000:1 GB(1 << 30);0x100000000 = 4 GB

有了锚点,陌生数字就能就近推算。看到 0x7FF,立刻知道约等于 2048(2¹¹ - 1)。

5.2 十六进制心算:一分为二

0xA5 = 10×16 + 5 = 165。任何十六进制数转十进制,就是逐位乘以 16 再加0x3F = 3×16 + 15 = 63。两位数的心算 3 秒,四位数拆成两个两位数再乘 65536。

5.3 二进制心算:从 128 往下数

8 位二进制转十进制,从最高位 128 开始往下试:10110101 → 128 + 32 + 16 + 4 + 1 = 181。熟练后,你看到 1001 1101 会直接反应”9 后面跟 D,那就是 0x9D = 157”——因为你已经学会用十六进制视角看二进制了。

心法:十六进制不是”另一种数”,而是”二进制的 4 位一格视图”。 用这个视角,位运算、掩码、内存地址全部豁然开朗。


六、进制在现实编程中的身影

6.1 C 语言的字面量

int a = 0xAD;     // 十六进制:前缀 0x
int b = 0255;     // 八进制:前缀 0
int c = 0b10101101;  // 二进制:C23 标准支持,GCC/Clang 早已作为扩展支持

经典面试题:010 == 10 吗?不! 010 是八进制,等于 8。这个”0 前缀”的设计每天坑着无数初学者,看到以 0 开头的整数字面量,请立刻想起八进制。

6.2 内存地址与调试器

你在 gdb 或 Windows 调试器里看到的地址,永远长这样:0x7ffd3a2b9c10。用十六进制而不是十进制显示,不是因为程序员装酷,而是因为每个十六进制位恰好对应 4 个二进制位——地址的本质是位模式,十六进制是与它对齐的镜子。

6.3 IP 地址与子网掩码

IPv4 的每个点分数字其实是一个 8 位二进制数:192.168.1.1 就是 11000000.10101000.00000001.00000001。子网掩码 255.255.255.0 更直白——那是 24 个连续的 1,写成 192.168.1.0/24,斜杠后面的 24 就是”前 24 位是网络位”。不看二进制,就永远理解不了 CIDR。

6.4 权限、颜色与 MAC 地址

  • chmod 7557 = 111(读+写+执行),5 = 101(读+执行)。每个八进制位就是三个二进制开关。
  • 网页颜色 #FF0000:红、绿、蓝各占 2 个十六进制位,#FF0000 是满红。
  • MAC 地址 00:1A:2B:3C:4D:5E:6 个字节,每组 2 个十六进制字符。

6.5 位运算与掩码

这是进制知识回报率最高的地方。想取一个数的最低 4 位?x & 0x0F。想取最低 8 位?x & 0xFF。想把某位置 1?x | (1 << 5)所有掩码都是十六进制常数,因为它们本质是”连续的 1 排成一行”——0x0F 一眼就能看出是 4 个 1,换成十进制 15 反而要反应一下。


七、思考

  1. 0x10 为什么是 16,而不是 10? 因为它每一位的权重是 16:1×16¹ + 0×16⁰ = 16。“10”这个写法在不同进制里是不同数值——这正是混用进制时最容易翻车的点。
  2. 010 在 C 语言里等于多少? 8。八进制字面量,经典陷阱。
  3. 0b11010010 对应的十六进制是多少? 四位一组:1101 00100xD2 = 210。
  4. 十进制的 0.1 能精确写成二进制吗? 不能,它是无限循环小数 0.0001100110011...。这个”装不下”的精度问题,是下一篇文章的主角。

八、总结

转换方法记忆
十进制 → 二进制除 2 取余,倒着读整数除法剥位
二进制 → 十进制按权展开,1 的位权相加背 2 的幂表
十进制小数 → 二进制乘 2 取整,顺着读小数乘法摘位
二进制 ↔ 十六进制四位一组,直接对照4 位 = 1 个 0x
二进制 ↔ 八进制三位一组,直接对照3 位 = 1 个八进制位
八进制 ↔ 十六进制先转二进制,再分组过桥
十进制 ↔ 八/十六除 8/16 取余,或过二进制同除 2 取余

一句话总结全文:

二进制是机器的语言,十进制是人类的习惯,八进制与十六进制是两者的速记本——进制只是数字的皮囊,数值从未改变;而”逢几进一”四个字,就是这整门学问的圆心。

下次你在调试器里看到 0x7ffd3a2b9c10 这样的地址,不要头晕——你能瞬间读出那是”以 16 进制写的位模式”,而再往下钻一层,那里住着的是一个又一个 0 和 1 组成的开关阵列。数字没有变,变的只是写法。

再往深处走一步:有了进制这个地基,下一篇我们聊聊计算机如何用这些 0 和 1 表示”小于 1”的数——以及为什么 0.1 + 0.2 ≠ 0.3

THAT’S END.

评论区