FlowingDev

数字进制详解:为啥计算机的计数方式那么奇葩

学习二进制、八进制、十进制和十六进制等数字进制系统如何以不同方式表示同一个数,这是计算领域的一个关键概念。

试用工具: Base Converter

一句话概括

数字进制转换就是一门翻译的艺术,它能将一个数字从一种计数系统(比如我们熟悉的十进制)转换到另一种(比如计算机的原生二进制),同时保持其数值不变。

它解决了什么问题

人类,绝大多数,有十根手指。这个天生的硬件配置使得以十为一组进行计数——也就是十进制或base-10系统——感觉无比自然。我们有十个独立的符号(0到9),一旦数到9,我们就要“进位”:在左边加个新数位,然后从零开始。十是 10,一百是 100,以此类推。小菜一碟。

而计算机,长着不一样的“手指”。它们最基本的硬件是晶体管,一种只能处于开或关两种状态的微型开关。没了,就这两种状态。这使得一个只有两个符号的系统——二进制或base-2——成了计算机的母语。这两个符号就是 0(关)和 1(开)。

这就产生了一个翻译问题。对计算机来说,人类世界的数字 237 毫无意义。它必须被表示成一系列的“开”和“关”。237 这个数,用二进制表示就是 11101101。

虽然计算机精通二进制,但人类……呃,并不。读一大长串的1和0简直是头痛和眼疲劳的催化剂。为了弥合这一差距,开发者们想出了一些方便的简写方式:八进制(base-8)和十六进制(base-16)。这些“2的幂”为基数的进制,能轻而易举地与二进制进行互转,同时能把超长的二进制字符串压缩成更易读的小块。

数字进制的存在,就是为了解决人类思考数字的方式与机器物理存储数字的方式之间的根本性错位。它们就像一块罗塞塔石碑,让我们能一窥机器的内心世界,而不至于迷失在1和0的海洋里。

底层工作原理

任何数字进制背后的魔法,都源于一个叫按位记数法(positional notation)的概念。一个数字的位置决定了它的值。咱们来拆解一下。

到底啥是“进制”?

想想你车里的里程表。当最右边的数字从9变到0时,它左边的数字就会加一。每个数字的“位置”都代表10的某个次方。

我们日常用的十进制数 427 实际上意味着:

  • 4 个一百 (4 × 10²)
  • 2 个十 (2 × 10¹)
  • 7 个一 (7 × 10⁰)

把它们加起来:400 + 20 + 7 = 427。

所谓的“进制基数”,就是我们在每个位置上进行幂运算的那个数。对于十进制,它是10。对于二进制(base-2),它是2。对于十六进制(base-16),它是16。公式是一样的,只是基数(b)变了:

... + (数字 × b²) + (数字 × b¹) + (数字 × b⁰)

从我们的世界(十进制)到它们的世界(任意进制)

我们来把十进制数 427 转换成二进制(base-2)。算法是“用目标进制的基数反复相除,并记录余数”。

除法运算 商 余数
427 ÷ 2 213 1
213 ÷ 2 106 1
106 ÷ 2 53 0
53 ÷ 2 26 1
26 ÷ 2 13 0
13 ÷ 2 6 1
6 ÷ 2 3 0
3 ÷ 2 1 1
1 ÷ 2 0 1

现在,从下往上读取余数。

所以,十进制的 427 就是二进制的 110101011。

这个方法适用于任何进制。要将 427 转换成十六进制(base-16),你只需除以16。(注意:在十六进制中,我们需要超过10个符号,所以我们用 A 代表10,B 代表11,...,直到 F 代表15)。

  • 427 ÷ 16 = 26 余 11(在十六进制中是 B)。
  • 26 ÷ 16 = 1 余 10(在十六进制中是 A)。
  • 1 ÷ 16 = 0 余 1。

从下往上读,我们得到 1AB。所以,十进制的 427 就是十六进制的 1AB。

从它们的世界(任意进制)回到我们的世界(十进制)

要反向转换,我们就用前面看到的按位记数法公式。让我们把二进制数 110101011 转回十进制。我们将每个数字乘以2的其位置次方(从右边第0位开始)。

  1 * 2^8 = 256
+ 1 * 2^7 = 128
+ 0 * 2^6 = 0
+ 1 * 2^5 = 32
+ 0 * 2^4 = 0
+ 1 * 2^3 = 8
+ 0 * 2^2 = 0
+ 1 * 2^1 = 2
+ 1 * 2^0 = 1
----------------
总和      = 427

搞定!我们再试试十六进制的 1AB:

  1 * 16^2 = 256
+ A * 16^1 = (10 * 16) = 160
+ B * 16^0 = (11 * 1)  = 11
-------------------------
总和       = 427

Duang!同一个数,只是换了身不同的马甲。

十六进制和八进制的快捷方式

那为啥还要费劲用十六进制和八进制呢?因为它们是“2的幂”为基数的进制。

  • 八进制是base-8,而 8 = 2³。这意味着每一个八进制数字都完美对应三位二进制数字。
  • 十六进制是base-16,而 16 = 2⁴。这意味着每一个十六进制数字都完美对应四位二进制数字(这四位被称为“半字节”或“nibble”)。

来看一个很长的二进制数: 1101011101001111 人类要读这个数,必须一个一个数字地看。但要把它转换成十六进制,你只需从右边开始,把它分成四位一组:

1101 | 0111 | 0100 | 1111

现在,转换每一组:

  • 1101 是 8+4+0+1 = 13,也就是 D
  • 0111 是 0+4+2+1 = 7
  • 0100 是 0+4+0+0 = 4
  • 1111 是 8+4+2+1 = 15,也就是 F

所以,1101011101001111 用十六进制简单表示就是 D74F。可读性无限提升,还不容易出错。这不仅仅是计算,这简直就是直接“转录”。这就是为什么开发者超爱十六进制:它是二进制数据最完美的人类可读包装。

真实世界的案例

神秘颜色代码事件

一位初级前端开发者从UI设计师那里收到了一个设计稿。品牌主色调被标注为 #E63946。这位开发者知道这是一种红色,但具体是多红?其他颜色成分呢?他们把这个十六进制代码扔进一个转换器。E63946 实际上是三个独立的十六进制数:E6 代表红色,39 代表绿色,46 代表蓝色。

  • 十六进制的 E6 转换成十进制是 230。好的,看来红色成分相当高(满值是255)。
  • 十六进制的 39 转换成十进制是 57。一点点绿色。
  • 十六进制的 46 转换成十进制是 70。一点点蓝色。

通过分解这个十六进制代码,开发者理解了颜色的构成。它不只是“红色”,而是“绝大部分是红色,带有一点点差不多量的绿色和蓝色,让颜色不那么刺眼。”

经验教训: 十六进制是Web上表示颜色的语言。理解它能帮助你超越简单的复制粘贴代码,真正理解你正在使用的颜色。

文件系统权限之谜

一位系统管理员正试图为一个新上传到Linux服务器上的脚本 deploy.sh 设置安全权限。他需要让文件所有者可以读、写、执行,但用户组和其他所有人都只能读和执行。他见过到处都在用 chmod 755 deploy.sh 这条命令,但一直以来都只是把它当作一句魔法咒语。

这次,他查了一下。数字 755 其实是八进制。它代表了三组权限:所有者(Owner)、用户组(Group)和其他人(Others)。

  • 所有者权限是 7。在二进制里,7 是 111。
  • 用户组权限是 5。在二进制里,5 是 101。
  • “其他人”权限是 5。在二进制里,5 是 101。

每个二进制位都对应一个特定的权限:读(r)、写(w)和执行(x)。

  • 111 意味着 rwx(读、写、执行权限全部开启)。
  • 101 意味着 r-x(读和执行权限开启,写权限关闭)。

所以,chmod 755 把权限设置成了 rwxr-xr-x,这正是他所需要的。谜团解开了。

经验教训: 八进制为管理文件权限提供了一种超级简洁的速记方式,而文件权限本质上就是一组开/关标志(位)。

位翻转引发的Bug

一位为智能恒温器开发嵌入式系统的开发者,需要将设备设置保存到一小块内存中。为了节省空间,他们用一个字节(8位)作为“标志寄存器”。每个位代表一个设置:isHeating(是否在加热)、isCooling(是否在制冷)、fanOn(风扇是否开启)、hasWifiConnection(是否有WiFi连接)等等。

一天,bug报告雪片般飞来:“我一开风扇,暖气也跟着启动了!” 开发者百思不得其解,开启风扇的代码看起来没问题。但当他们检查内存中的设置字节时,发现了问题所在。假设风扇是第2个位(值 2¹ = 2),暖气是第3个位(值 2² = 4)。代码本应将字节设置为 ...010(风扇开)。但实际上,它却设置成了 ...110(风扇开 并且 暖气开)。对应的十进制值本应是 2,结果却成了 6,这并不直观。但一看二进制表示 00000110,问题就一清二楚了:有两个位被翻转了,而不是一个。罪魁祸首是一个错误的位运算操作。

经验教训: 对于底层编程、内存调试或处理硬件寄存器,用二进制思考不是可选项——它是看清实际情况的唯一方式。

常见错误和陷阱

  • 忘了加前缀。 在许多编程语言(如C、Java或JavaScript)中,以 0 开头的数字会被解释为八进制。010 不是十,是八!以 0x 开头的数字是十六进制。0x10 是十六。不知道这一点会导致一些非常诡异的bug。
  • 混淆 O/0 和 I/1。 当你盯着一长串十六进制或二进制字符串时,很容易眼花,把字母 O 当成零,或者把 I 当成一。十六进制不使用 O 或 I,但其他进制转换可能会,这是一个经典的“手滑”错误。
  • 想当然地认为大于16的进制有标准。 二进制(0-1)、八进制(0-7)、十进制(0-9)和十六进制(0-9, A-F)的字符集是标准化的。但base-22或base-36呢?大多数系统使用 0-9 加上 A-Z,但这并非宇宙通行法则。在处理不常见的进制时,一定要核实所用的字符集(或称“字母表”)。
  • 忽略整数限制。 一个64位的十六进制数,如 0x7FFFFFFFFFFFFFFF,代表一个极其巨大的数字。如果你试图转换它并将其存储在你程序的一个标准32位整型变量中,它会放不下。这会导致“整数溢出”,数字会绕圈变成一个负数,或者直接报错。

为什么你应该关注它

即使你不是一个底层C程序员,你也会不断地碰到不同的数字进制。每当你做以下事情时,都应该想到它们:

  • 在CSS或设计工具中挑选颜色(#RRGGBB)。
  • 处理哈希值,比如API密钥或Git提交的SHA值,它们几乎总是十六进制的。
  • 在Web服务器上设置权限(chmod)。
  • 处理底层数据格式、网络协议或任何使用位掩码(bitmask)的东西(例如,功能开关)。
  • 在调试器中遇到错误代码或内存地址。
  • 试图理解计算机究竟是如何工作的。这是一个基础概念,它揭开了许多数字魔法的神秘面纱。

深入探索

理论搞定,动手试试吧——100% 在你的浏览器中运行。

试用工具: Base Converter