一句话概括
进制转换器可以在不同的计数系统之间转换数字,比如我们日常使用的熟悉的十进制 (base-10),以及驱动所有数字逻辑的二进制 (base-2)。
它解决的问题
人类对数字十有种执念。我们有十个手指和十个脚趾,所以用十个一组来计数(也就是十进制或 base-10 系统)感觉就像呼吸一样自然。但计算机呢?它们要简单一些。在核心层面,它们只懂两件事:“开”和“关”。有电流通过,或者没有。晶体管是导通的,或者不是。这就是二进制(base-2)的世界。计算机里的每一条信息——这段文本、你最爱的猫咪视频、操作系统本身——都只是一串长到无法想象的 1 和 0。
这就造成了文化冲突。在计算的早期,程序员必须盯着这些巨大的二进制代码墙来调试程序。想象一下,试图在一个由 10110010... 组成的百万字符的字符串里找一个拼写错误。这简直令人抓狂、极易出错,也是导致程序员拿头撞墙的主要原因之一。
为了保住他们的理智(以及额头),他们发明了简写方法。他们意识到可以把比特(bit)组合在一起。按三位分组得到了八进制 (base-8),按四位分组得到了十六进制 (base-16)。突然之间,一长串二进制序列,比如 1011001011111110,就可以用更紧凑的十六进制形式 B2FE 来书写。
进制转换器就是为了解决这个根本性的转换问题而存在的。它是在人类思想的十进制世界与我们硅基仆人所处的二进制、八进制和十六进制世界之间的通用外交官。
底层工作原理
在不同进制之间转换的魔法根本不是魔法——它只是一种叫做位值表示法的计数系统。
“进制”到底是个啥?
在任何进制中,数字的位置决定了它的值。我们在十进制中认为这是理所当然的。在数字 345 中,我们凭直觉就知道它表示“三个一百、四个十和五个一”。
数学上,就是:
(3 * 10^2) + (4 * 10^1) + (5 * 10^0)
= (3 * 100) + (4 * 10) + (5 * 1)
= 300 + 40 + 5 = 345
“进制”(base)就是你为每个位置计算乘方时用的那个数(在这里是 10)。同样的规则适用于任何进制。
在二进制 (base-2) 中,我们只有两个数字:0 和 1。二进制数 1011 表示的是:
(1 * 2^3) + (0 * 2^2) + (1 * 2^1) + (1 * 2^0)
= (1 * 8) + (0 * 4) + (1 * 2) + (1 * 1)
= 8 + 0 + 2 + 1 = 11 (十进制)
十六进制 (base-16) 有点奇特,因为我们需要 16 个不同的“数字”。我们用 0-9 表示前十个,然后借用字母 A-F 来表示 10-15。
| 十进制 | 十六进制 | 二进制 |
|---|---|---|
| 0 | 0 | 0000 |
| 1 | 1 | 0001 |
| ... | ... | ... |
| 9 | 9 | 1001 |
| 10 | A | 1010 |
| 11 | B | 1011 |
| 12 | C | 1100 |
| 13 | D | 1101 |
| 14 | E | 1110 |
| 15 | F | 1111 |
从十进制转换为任意进制
要反过来——从我们熟悉的十进制转换到另一个进制——我们使用重复相除法。让我们把十进制的 187 转换为十六进制 (base-16)。
- 将 187 除以 16:
187 / 16 = 11,余数为11。在十六进制中,11 是B。 - 将结果 (11) 除以 16:
11 / 16 = 0,余数为11。在十六进制中,11 是B。 - 从下往上读取余数:
BB。
所以,十进制的 187 就是十六进制的 BB。
特殊关系:二进制、八进制和十六进制
这就是早期程序员发现的绝妙捷径。因为 8 = 2^3 且 16 = 2^4,所以你可以从二进制转换到八进制或十六进制,而完全无需经过十进制。你只需将比特分组!
以二进制数 110101110100 为例。
转换为十六进制 (base-16): 从右边开始,将比特按四位分组。
1101 0111 0100然后,转换每一组:1101=D(13)0111=70100=4结果:十六进制的D74。转换为八进制 (base-8): 从右边开始,将比特按三位分组。
110 101 110 100然后,转换每一组:110=6101=5110=6100=4结果:八进制的6564。
这就是为什么十六进制和八进制如此受欢迎。它们是原始二进制数据的紧凑、人类可读的表示形式。
位运算
程序员的计算器不止是简单的进制转换。它还能执行位运算 (bitwise operations),这些运算直接操作一个数的二进制表示中的单个 1 和 0。
- AND (
&): 如果两个操作数的对应位都为 1,则结果的该位为 1。这对于“屏蔽”(masking) 或检查特定位是否开启非常有用。1101 & 1011 = 1001 - OR (
|): 如果两个操作数的对应位中有一个或两个都为 1,则结果的该位为 1。用于将某个位“设置”为开启。1101 | 1011 = 1111 - XOR (
^): 当两个操作数的对应位不同时,结果的该位为 1。这是一种切换比特位的便捷方法。1101 ^ 1011 = 0110 - NOT (
~): 翻转所有的比特位。~1101 = 0010(假设是 4 位数) - 位移 (
<<,>>): 将所有比特位向左或向右移动。左移 (<< 1) 是快速乘以 2 的方法;右移 (>> 1) 是快速除以 2 的方法。
实战故事
神秘文件类型之谜
一个开发者收到一封邮件,附件名为 data.bin。它应该是一张图片,但她的电脑不知道怎么打开。她没有放弃,而是在一个十六进制查看器中打开了文件。文件开头的字节是 89 50 4E 47 0D 0A 1A 0A。她以前见过这种像乱码一样的东西。她把前四个字节 89504E47 扔进搜索引擎。结果立刻就出来了:这是一个 PNG 图片的“魔数”(magic number),或者叫文件签名。她把文件名重命名为 data.png,然后,瞧!文件打开了,显示出一张戴着帽子的柴犬的超赞照片。
经验之谈:十六进制是原始数据的语言。理解它能让你在最基础的层面识别和调试文件。
CSS 颜色之谜
一位前端开发者快被逼疯了。她在 CSS 里指定了一种漂亮鲜艳的红色:color: #FF0000;。但在屏幕上,颜色却是一种稍微暗淡、有点偏的红色。她用浏览器的开发者工具检查元素,发现计算出的颜色实际上是 #FE0000。她用进制转换器确认,FF 是十进制的 255(一个颜色通道的最大值),而 FE 是 254。差别很小,但确实存在。她顺着代码往回追溯颜色值,发现一个函数在将浮点数转换为整数时用了 Math.floor() 而不是 Math.round()。一个像 254.99 这样的值就变成了 254 (FE) 而不是 255 (FF)。快速修复后,她的红色变得完美而鲜艳。
经验之谈:Hex 是网页颜色的通用语。能够快速转换和理解十六进制值对于实现像素级完美的设计和调试至关重要。
在 Linux 服务器上设置权限
一个初级系统管理员需要为一个服务器上的配置文件设置安全权限。高级管理员告诉他:“把权限设置为 rwx r-x ---。” 这位初级管理员知道这对应于所有者、用户组和其他人的权限,但他忘了命令。他记得 Linux 权限可以用八进制表示。他把它分解开来:
- 所有者:
rwx(读、写、执行) =4 + 2 + 1 = 7 - 用户组:
r-x(读、执行) =4 + 0 + 1 = 5 - 其他人:
---(无权限) =0 + 0 + 0 = 0
他把它们组合起来得到八进制数 750。他自信地敲下 chmod 750 config.yml,并得到了他上司的点头认可。
经验之谈:八进制提供了一种简洁、标准的方式来表示位掩码 (bitmasks),这在像 Unix 文件权限这样的底层系统中很常见。
常见错误和陷阱
- 忘记前缀: 数字
10在十进制、二进制和十六进制中意味着截然不同的东西。代码和文档中使用0x作为十六进制前缀(0x10= 16)、0b作为二进制前缀(0b10= 2)、0o作为八进制前缀(0o10= 8)来避免歧义。务必注意上下文。 - 混淆相似字符: 当你累了,盯着内存转储时,很容易把
B和8,或者D和0搞混。仔细检查你抄录的十六进制数。 - 忽略有符号性(二进制补码): 一个表示为
0xFFFF的 16 位十六进制数,如果它是一个无符号整数,可能意味着65535。但如果它是一个使用常见的“二进制补码”(two's complement)系统的有符号整数,它代表的是-1。比特位的含义完全取决于它们如何被解释。 - 字节序(Endianness)之苦: 当一个像
0x1A2B3C4D这样的多字节数存储在内存中时,字节的顺序可能会不同。“大端序”(Big-endian)系统将其存储为1A 2B 3C 4D。“小端序”(Little-endian)系统(像大多数现代个人电脑)则将其存储为4D 3C 2B 1A。当两个不同字节序的系统试图通信时,这是网络和文件解析中一个典型的 bug 来源。
为何你应该关注它
每当你在高级抽象的表层之下窥探时,都应该用不同的数字进制来思考。这包括:
- 分析网络数据包(例如,MAC 地址、IPv6 地址)。
- 处理文件格式或原始内存转储。
- 处理几乎总是以十六进制显示的 hash 输出(例如,SHA-256)。
- 微调网页颜色或位图图形。
- 使用位运算(bitwise operations)编写性能关键的代码。
- 配置底层系统设置,如权限或硬件寄存器。
理解进制是开发者的一项超能力。它弥合了你写的代码和赋予它生命的电脉冲之间的鸿沟。
深入了解
- 维基百科上的位值表示法 (Positional notation) — 所有数字进制系统的数学基础。
- 维基百科上的十六进制 (Hexadecimal) — 深入探讨 base-16 的历史及其常见用途。
- 维基百科上的二进制补码 (Two's Complement) — 关于计算机如何在二进制中表示负数的权威解释。
- MDN 上的 JavaScript 位运算符 (Bitwise Operators) — 在一种流行语言中使用位运算的实用指南。
- 维基百科上的文件签名列表 (List of file signatures) — 一个通过“魔数”(通常用十六进制表示)识别文件的便捷参考。
- 维基百科上的字节序 (Endianness) — 了解为什么字节的顺序比你想象的更重要。