D
开发工具箱

进制转换的本质:为什么计算机用二进制而人类用十进制

编程转换 2026年6月15日 约 1 分钟阅读

什么是进制转换?

进制转换,说白了就是把一个数从一种计数规则翻译成另一种计数规则。十六进制的 FF 和十进制的 255 是同一个数,只是穿的衣服不一样。

但为什么会有这么多套”衣服”?根源在于人类和计算机的底层硬件对数字的感知方式完全不同。我们天生有十根手指,十进制就自然成了人类的母语进制。计算机呢?晶体管只有通和断两种状态,所以它只认二进制。

于是问题来了:程序员要在人类和计算机之间做翻译。直接读二进制——比如 11111111——太费眼了。所以就有了八进制和十六进制,它们是二进制的”缩写语言”。一个十六进制字符正好对应 4 个二进制位,两个十六进制字符正好一个字节,不多不少。这个完美映射,就是 Hex 在编程世界里无处不在的根本原因。

我第一次真正意识到进制不是教科书里的抽象概念,是在配一台 Linux 服务器的时候。执行 chmod 755 script.sh 之后脚本就能跑了,我当时只知道”755 代表权限”,完全没去想为什么偏偏是这三个数字。后来才知道,这是八进制——7=111(读+写+执行),5=101(读+执行),每个数字恰好 3 个 bit,和权限位的 rwx 完美对应。这个设计简直漂亮。

工作原理:几套进制为什么长那样

四种进制速览

进制基数数码前缀用途
二进制20, 10b位运算、底层硬件
八进制80-70oUnix 文件权限
十进制100-9人类日常
十六进制160-9, A-F0x内存地址、哈希、数据 dump

0b0o0x 这三个前缀不是数据本身,而是给编译器和读代码的人一个信号——“后面的数字请按特定进制理解”。

0b1010   // 二进制 → 十进制 10
0o644    // 八进制 → 十进制 420
0xFF     // 十六进制 → 十进制 255

进制转换的通用算法

不管什么进制,转换的逻辑都一样——把生活里的十进制想象成”中转站”:

任意进制转十进制:按权展开。每一位的值乘上基数的对应次幂,全加起来。

二进制 1101 = 1×2^3 + 1×2^2 + 0×2^1 + 1×2^0 = 13
十六进制 2A  = 2×16^1 + 10×16^0 = 42
八进制 77    = 7×8^1 + 7×8^0 = 63

十进制转任意进制:不断除以目标基数,记余数,倒序读。

42 转十六进制:
42 ÷ 16 = 2 余 10 → A
 2 ÷ 16 = 0 余  2 → 2
结果:2A

这个算法七八十行代码就能写出来,但你大概率不需要自己写。JavaScript 内置了两个专门干这个的方法。

JavaScript 的 parseInt 和 toString

// 任意进制 → 十进制
parseInt("FF", 16);       // 255
parseInt("1010", 2);      // 10
parseInt("77", 8);        // 63

// 十进制 → 任意进制(radix 范围 2-36)
(255).toString(16);       // "ff"
(255).toString(2);        // "11111111"
(255).toString(8);        // "377"

有一个我自己踩过的坑——parseInt 在不传第二个参数的时候会自作聪明。比如 parseInt("077") 在某些旧引擎里会被当作八进制解析,返回 63 而不是 77。所以 parseInt 的 radix 参数实际上不应该省略——永远写成 parseInt(str, 10) 或指定你要的进制,别让引擎替你猜。说实话,ES5 之后已经废除了默认八进制的行为,但这不代表你可以偷懒——代码的可读性本身就是一种安全。

为什么 Hex 是程序员最常用的进制?

想象你在调试一段内存数据,有 4 个字节:10100111 11111111 00000000 01111111。盯久了眼睛疼。

写成 Hex:A7 FF 00 7F。两个字符一个字节,排列整齐,一眼能看出边界。写成十进制的话——乱七八糟,完全看不出字节结构。所以 hex dump 工具几乎清一色用十六进制加 ASCII 对照栏的布局,三栏一目了然。

八进制其实也有它的妙处,只是场景窄。3 位一组的设计让它天生适合表示 3-bit 的权限位。chmod 7557rwx(读 4 + 写 2 + 执行 1),5r-x。换成十六进制反而别扭——你不会想写 chmod 0x1ED。所以不是 Hex 在所有场景都最优,是看你的数据结构和进制宽度是否对齐。

核心特性

特性说明
本质同一数值的不同表示形式,不改变数值本身
二进制与 Hex 的映射4 bit = 1 Hex 字符,完美对齐字节边界
八进制与权限的映射3 bit = 1 个八进制数码,天然对应 rwx
JavaScript 支持parseInt(str, radix)num.toString(radix),radix 范围 2-36
前缀约定0b 二进制、0o 八进制、0x 十六进制(ES6 标准化)
可逆性进制转换完全可逆,无信息损失,和加密/哈希不同

实际应用场景

1. Unix 文件权限(八进制)

前面提到的 chmod 755 本质上是一次八进制到二进制的隐式转换。每个数字展开成 3 位二进制,分别控制读(r=4)、写(w=2)、执行(x=1):

7 = 111 → rwx(所有者有全部权限)
5 = 101 → r-x(用户组有读和执行)
5 = 101 → r-x(其他人有读和执行)

八进制在这里比十六进制自然,因为 3 位刚好是一个权限组。这不是巧合,是特意选的设计。

2. 颜色代码(十六进制)

#FF5733 这样的 CSS 颜色码,背后是三个十六进制字节——红通道 FF(255)、绿通道 57(87)、蓝通道 33(51)。每个通道 8 位,正好两个 Hex 字符,共 24 位表示 1600 多万种颜色。

3. 内存地址和调试(十六进制)

debug 时看到的地址清一色是 Hex:0x7ffc8b3a2100。这不是装酷——内存地址天然是 2 的幂的倍数,用十六进制一眼能看出边界和范围,用十进制则会丢失这种结构感。

4. 位掩码和标志位(二进制)

JavaScript 里位运算并不常见,但它确实是一个干净的解——用单个整数承载多个布尔标志:

const READ = 0b001;   // 1
const WRITE = 0b010;  // 2
const EXEC = 0b100;   // 4

let perm = READ | WRITE;          // 0b011 = 3
const canExec = (perm & EXEC) !== 0;  // false

二进制字面量 0b 前缀在这里让标志位的定义一目了然,比写十进制 1, 2, 4 清楚得多。

常见误区

误区一:进制转换会改变数值

不会。0xFF2550b11111111 是同一个数。改变的只是书写形式。这跟汇率换算有点像——100 人民币和 14 美元,币种不同,价值不变。进制就是数字的”币种”。

误区二:parseInt 不传第二个参数没关系

前面说过,这个习惯非常不好。parseInt("08") 在老引擎里可能返回 0(当成无效八进制),ES5 后虽然修了,但 parseInt("0x10") 仍然会被当成十六进制返回 16。如果你的输入来自用户,永远写成 parseInt(input, 10)

误区三:进制前缀在所有语言里都一样

0x 作为十六进制前缀几乎是通用的,但八进制各语言的写法差别很大。C 语言里 077 就是八进制(一个前导零),Go 和现代 JS 用 0o77,Python 3 也是 0o77。我写过一阵 Go 之后回头写 C,在数字前面加了个 0o,编译直接报错——语言迁移时最容易在细节上翻车。

进制之间的对比

二进制八进制十进制十六进制
基数281016
单字节表示长度8 字符3 字符(不对齐)1-3 字符2 字符
与字节的对齐直接否(3 bit 组)完美(4 bit 组)
可读性一般最好(人类)好(程序员)
典型场景位运算chmod 权限日常计算内存、哈希、颜色
JS 前缀0b0o0x

常见问题

Q: 为什么 JavaScript 的 toString(radix) 上限是 36?

因为 0-9 十个数字加上 a-z 二十六个字母,一共正好 36 个可用符号。高于 36 的基数需要发明新符号,语言设计者觉得没必要。

Q: 0xFF\xFF 是一回事吗?

不是。0xFF 是数值字面量,\xFF 是字符串里的十六进制转义序列,表示 ASCII 码为 255 的那个字符。场景不同,语法不同。

Q: 八进制在 2026 年还有必要学吗?

如果你做后端或者运维,绝对有。Linux 文件权限、umask、某些嵌入式系统的寄存器定义,全是八进制。不学的话每次都要掏出计算器现查,效率极低。

Q: 写代码时到底该用哪种进制?

看数据结构的自然宽度。字节级别用 Hex(两个字符一个字节),3-bit 权限组用八进制,多个布尔标志用二进制字面量。一种进制不统治所有场景——够用就行,别教条。

Q: 浮点数可以做进制转换吗?

可以,但比整数麻烦得多。小数部分的转换是乘目标基数取整,而不是除基取余。而且十进制的小数转二进制经常出现无限循环——0.1 在二进制下是 0.0001100110011...,这就是 0.1 + 0.2 !== 0.3 的根本原因。