进制转换完全指南:二进制、十六进制、八进制与十进制互转
下午三点,你在调试器里盯着 0x7FFF5FBFF8C0,切到 CSS 文件改了个 #FF5733,又在终端里敲了 chmod 755。三种不同的数字写法,底层是同一套算术。如果你在十六进制和二进制之间换算时需要停下来想一想,或者好奇 Unix 权限为什么偏偏用八进制——这篇文章能帮你把这些知识串起来。
我们会讲清楚编程中最常见的四种进制、三种值得记住的转换方法,还有 JavaScript、Python、Go 和 C 的实际代码。如果只想快速转个数字,直接打开进制转换工具——支持 2 到 36 任意进制,任意精度。
开发者常用的四种进制
每种进制在编程中存在都有实际原因,不是历史包袱。
二进制(Base 2)——机器的语言
两个数字:0 和 1。晶体管要么导通要么截止,这个物理限制决定了二进制的存在。你在处理位掩码、特性标志、位运算和 IP 子网计算时会直接跟二进制打交道。
二进制写起来很长。十进制的 255 在二进制里是 11111111——八位数字才表示一个三位的十进制值。这就是程序员很少直接写二进制的原因,除非每个 bit 的位置都有意义。
八进制(Base 8)——Unix 的速记法
八个数字:0 到 7。每个八进制位恰好对应三个二进制位,这就是 Unix 文件权限用八进制的原因——chmod 755 把三组三位权限压缩成三个可读的数字。
八进制在 PDP-11 时代用得更广泛,那时机器字长刚好能被 3 整除。现在它主要出现在 Unix 权限里,偶尔还有 C 语言中以 0 开头的字面量(不少人写 0177 以为是十进制 177,结果踩了坑)。
十进制(Base 10)——人类的默认项
十个数字:0 到 9。你的大脑默认用十进制思考——端口号、数组下标、HTTP 状态码、像素尺寸全是十进制。计算机内部并不用十进制,但人类用,所以所有面向用户的数字都以 Base 10 呈现。
十六进制(Base 16)——开发者的瑞士军刀
十六个符号:0-9 加 A-F。每个十六进制位恰好代表四个二进制位(一个 nibble),两个十六进制位就是一个字节,永远如此。
你会在内存地址(0x7FFF5FBFF8C0)、CSS 颜色(#FF5733)、MAC 地址(00:1A:2B:3C:4D:5E)、UUID 格式和哈希摘要中遇到十六进制。它是字节级编程的通用语言。
用进制转换工具试一试——输入任意进制的数字,其他进制的结果实时更新。
进制转换的三种核心方法
不用工具也能手动转换,掌握三种方法就够了。
方法一——按位展开(任意进制 → 十进制)
所有位值计数系统的原理相同:每一位的值等于该位数字乘以”进制的该位次方”,从右往左、从 0 开始计数。
二进制 1011:
1×2³ + 0×2² + 1×2¹ + 1×2⁰
= 8 + 0 + 2 + 1
= 11
十六进制 FF:
15×16¹ + 15×16⁰
= 240 + 15
= 255
代码里大多数语言都有现成的解析函数:
parseInt('1011', 2) // 11
parseInt('FF', 16) // 255
parseInt('755', 8) // 493
方法二——连除取余(十进制 → 任意进制)
反向操作:用十进制数不断除以目标进制,收集余数,从下往上读。
十进制 255 → 十六进制:
255 ÷ 16 = 15 余 15 (F)
15 ÷ 16 = 0 余 15 (F)
→ 从下往上读:FF
十进制 42 → 二进制:
42 ÷ 2 = 21 余 0
21 ÷ 2 = 10 余 1
10 ÷ 2 = 5 余 0
5 ÷ 2 = 2 余 1
2 ÷ 2 = 1 余 0
1 ÷ 2 = 0 余 1
→ 从下往上读:101010
bin(42) # '0b101010'
hex(255) # '0xff'
oct(493) # '0o755'
方法三——位分组(二进制 ↔ 十六进制/八进制直接转)
这是熟练开发者最常用的方法。因为 16 = 2⁴、8 = 2³,二进制和十六进制(或八进制)之间可以通过分组直接转换,不需要做除法。
二进制 → 十六进制: 从右往左每 4 位一组(nibble),不够的左边补零。
二进制:1010 1111
十六进制:A F
→ AF
二进制 → 八进制: 从右往左每 3 位一组。
二进制:111 101 101
八进制: 7 5 5
→ 755
这张 nibble 对照表值得背下来:
| 二进制 | 十六进制 | 二进制 | 十六进制 |
|---|---|---|---|
0000 | 0 | 1000 | 8 |
0001 | 1 | 1001 | 9 |
0010 | 2 | 1010 | A |
0011 | 3 | 1011 | B |
0100 | 4 | 1100 | C |
0101 | 5 | 1101 | D |
0110 | 6 | 1110 | E |
0111 | 7 | 1111 | F |
记住这张表之后,二进制转十六进制就是扫一眼的事。
各语言的进制转换代码
四种语言中进制转换出现频率最高的写法。
JavaScript / TypeScript
// 解析:任意进制字符串 → 数字
parseInt('FF', 16) // 255
parseInt('101010', 2) // 42
parseInt('755', 8) // 493
// 格式化:数字 → 任意进制字符串
(255).toString(16) // 'ff'
(42).toString(2) // '101010'
(493).toString(8) // '755'
// 字面量
const bin = 0b11111111; // 255
const oct = 0o377; // 255
const hex = 0xff; // 255
// BigInt 处理超过 2⁵³ 的值
const big = BigInt('0xFFFFFFFFFFFFFFFF');
big.toString(2) // 64 个 1
big.toString(10) // '18446744073709551615'
Python
# 十进制 → 其他进制(返回带前缀的字符串)
bin(255) # '0b11111111'
oct(493) # '0o755'
hex(255) # '0xff'
# 其他进制 → 十进制
int('11111111', 2) # 255
int('FF', 16) # 255
int('755', 8) # 493
# 格式化输出(带填充)
f'{255:08b}' # '11111111'(8 位二进制,补零)
f'{255:02x}' # 'ff'(2 位十六进制,小写)
f'{255:02X}' # 'FF'(2 位十六进制,大写)
# Python 整数天生支持任意精度
big = int('F' * 64, 16) # 256 位数字,不会溢出
Go
package main
import (
"fmt"
"strconv"
)
func main() {
// 格式化:int → 任意进制字符串
fmt.Println(strconv.FormatInt(255, 16)) // "ff"
fmt.Println(strconv.FormatInt(255, 2)) // "11111111"
fmt.Println(strconv.FormatInt(493, 8)) // "755"
// 解析:任意进制字符串 → int
n, _ := strconv.ParseInt("FF", 16, 64) // 255
fmt.Println(n)
// Printf 格式化动词
fmt.Printf("%b %o %x %d\n", 255, 255, 255, 255)
// 11111111 377 ff 255
}
C
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main() {
// 按十进制、八进制、十六进制输出
printf("%d %o %x\n", 255, 255, 255);
// 255 377 ff
// 从任意进制解析
long val = strtol("FF", NULL, 16); // 255
long bin = strtol("101010", NULL, 2); // 42
// C 没有内置二进制输出——手动逐位提取:
uint8_t byte = 0xAF;
for (int i = 7; i >= 0; i--)
putchar(((byte >> i) & 1) ? '1' : '0');
// 10101111
return 0;
}
实战中的进制转换场景
五个进制转换不再是课本知识、而是工作内容的真实场景。
1. 调试内存地址
调试器显示指针在 0x7FFF5FBFF8C0。把最后两个字节转成二进制——1000 1100 0000——可以看出地址对齐到了 64 字节边界(六个尾零)。对齐对缓存性能、SIMD 操作和内存映射 I/O 都有影响。十六进制让这些模式一目了然。
指针算术用十六进制也更好算。基地址偏移 0x100 就是 256 字节——在很多嵌入式系统里刚好是一页。用十进制就没这么直观了。
2. CSS 十六进制颜色 ↔ RGB
颜色 #FF5733 是三个字节按十六进制对排列:
| 对 | 十六进制 | 十进制 | 通道 |
|---|---|---|---|
FF | FF | 255 | 红色(满值) |
57 | 57 | 87 | 绿色 |
33 | 33 | 51 | 蓝色 |
简写 #F00 展开为 #FF0000——纯红。八位格式 #FF573380 增加了 alpha 通道,80(十进制 128)大约是 50% 透明度。
理解十六进制和十进制的映射关系,能省掉很多打开取色器微调数值的时间。
3. Unix 文件权限(八进制)
chmod 755 拆解:
7 → 111 → rwx(所有者:读 + 写 + 执行)
5 → 101 → r-x(组:读 + 执行)
5 → 101 → r-x(其他人:读 + 执行)
每个八进制数字编码一组权限,因为三个权限位(读=4,写=2,执行=1)恰好对应一个八进制位(0-7)。常见组合:
| 八进制 | 二进制 | 权限 | 典型用途 |
|---|---|---|---|
755 | 111 101 101 | rwxr-xr-x | 可执行文件、目录 |
644 | 110 100 100 | rw-r--r-- | 普通文件 |
700 | 111 000 000 | rwx------ | 私有脚本 |
600 | 110 000 000 | rw------- | SSH 密钥、敏感文件 |
4. 网络子网计算
/24 子网掩码意味着二进制中有 24 个前导 1:
11111111.11111111.11111111.00000000
→ 255.255.255.0
要算出网络地址,把 IP 和掩码按位 AND:
192.168.1.37 → 11000000.10101000.00000001.00100101
255.255.255.0 → 11111111.11111111.11111111.00000000
AND 结果 → 11000000.10101000.00000001.00000000
→ 192.168.1.0(网络地址)
网络工程师日常在十进制(IP 表示法)、二进制(子网计算)和十六进制(抓包分析)之间来回切换。用进制转换工具可以逐个八位组验算。
5. 读取哈希摘要和 UUID
MD5 哈希如 d41d8cd98f00b204e9800998ecf8427e 是 32 个十六进制字符,代表 16 字节(128 位)。每对十六进制数字就是一个字节。
UUID 遵循 8-4-4-4-12 的十六进制格式:
550e8400-e29b-41d4-a716-446655440000
32 个十六进制数字加连字符——同样是 128 位,只是分段方式不同。第 13 位的 4(41d4 中的 4)标识这是 UUID v4。
用 MD5 & SHA 哈希生成器生成哈希,或用 UUID 生成器创建 UUID——两者都输出十六进制,直接对应本文讨论的二进制表示。
超越 Base 16:Base 36、Base 64 和自定义进制
标准四种进制覆盖了大部分工作,但有几种特殊进制也会出现。
Base 36——紧凑的字母数字编码
Base 36 使用 10 个数字加 26 个字母(A-Z),是大小写不敏感下最紧凑的字母数字表示。短链接服务很喜欢它——YouTube 视频 ID、短网址和数据库短键经常用 Base 36。
(1000000).toString(36) // 'lfls'
parseInt('lfls', 36) // 1000000
十进制 1,000,000 压缩到四个字符。作为 URL 友好的短标识,很难找到更紧凑的方案。
Base 64——数据编码(不是计数系统)
Base64 看起来像另一种进制,但用途完全不同。它不是把数值用位值系统表示,而是把任意二进制数据(图片、文件、JWT 令牌)编码成 ASCII 文本。使用 A-Z、a-z、0-9、+ 和 /——共 64 个符号。
它的数学原理和进制转换不同。Base64 以 3 字节(24 位)为一组输入,输出四个 6 位字符。这是编码方案,不是计数系统。
编解码 Base64 数据可以用Base64 编解码工具。
任意进制(2-36)出现的场合
还有几种进制偶尔会冒出来:
- Base 12(十二进制): 时间(12 小时制)、计量(一打)。有数学家认为 Base 12 比 Base 10 更适合日常使用,因为 12 的因子更多。
- Base 60(六十进制): 时间(60 秒、60 分钟)和角度(360°)。继承自巴比伦数学。
- Base 32: Crockford 的 Base32 编码用于人类可读标识符(排除了 I、L、O 等容易混淆的字符),也用于 geohash。
进制转换工具支持 2 到 36 的任意整数进制。
位运算与进制转换
理解二进制不只是为了转数字。位运算建立在二进制之上,而系统编程、游戏开发和权限系统里全是位运算。
const READ = 0b100; // 4
const WRITE = 0b010; // 2
const EXEC = 0b001; // 1
// 用 OR 组合权限
const perms = READ | WRITE; // 0b110 = 6
// 用 AND 检查权限
(perms & READ) !== 0 // true — 有读权限
(perms & EXEC) !== 0 // false — 没有执行权限
// 用 XOR 切换权限
perms ^ WRITE // 0b100 = 4 — 写权限被移除
// 移位操作
1 << 3 // 0b1000 = 8(1 左移 3 位)
0xFF >> 4 // 0b00001111 = 15(右移 4 位 = 除以 16)
特性标志、硬件寄存器、网络协议、图形编程——全靠位运算。二进制读熟了,位操作就是白纸黑字的逻辑,没什么玄的。
FAQ
编程中最常用的四种进制是什么?
二进制(Base 2)、八进制(Base 8)、十进制(Base 10)和十六进制(Base 16)。二进制是数据在硬件中的物理存在形式,八进制对应 Unix 文件权限,十进制是人类默认的表示方式,十六进制把二进制压缩成可读格式——每个十六进制位恰好是 4 个二进制位。
怎么把二进制转成十六进制?
从右往左每 4 位分成一组,最左边不够的补零。然后查表:0000=0、0001=1、…… 1010=A、…… 1111=F。举例:二进制 10101111 → 分组 1010 1111 → 十六进制 AF。原理是 16 = 2⁴。
怎么把十六进制转成十进制?
每个十六进制位乘以 16 的对应次方(最右边是 0 次方),然后求和。记住 A=10、B=11、C=12、D=13、E=14、F=15。举例:十六进制 FF = 15×16¹ + 15×16⁰ = 240 + 15 = 255。代码中:JavaScript parseInt('FF', 16),Python int('FF', 16)。
为什么程序员用十六进制而不用二进制?
十六进制紧凑。每个十六进制位正好对应 4 个二进制位,所以二进制的 11111111 00001010 写成十六进制就是 FF0A——短得多,也更容易扫读。十六进制是内存地址、CSS 颜色(#FF5733)、MAC 地址、哈希输出和 UUID 格式的标准表示。
什么是 nibble?它跟十六进制转换有什么关系?
Nibble 是 4 个二进制位,即半个字节。一个 nibble 恰好对应一个十六进制位,所以二进制转十六进制就是逐 nibble 查表。一个字节 = 两个 nibble = 两个十六进制位。这种整齐的 4 位映射正是十六进制成为字节级数据标准表示的原因。
Unix 文件权限为什么用八进制表示?
每组权限有三个位:读(4)、写(2)、执行(1),共三组(所有者、组、其他人)。因为 2³ = 8,三个权限位恰好对应一个八进制位。755 表示所有者=7(rwx)、组=5(r-x)、其他人=5(r-x)。八进制天然适合 3 位一组的编码。
JavaScript 中怎么处理超过 2⁵³ 的大数字?
用 BigInt。在数字后加 n 或用 BigInt() 构造函数:BigInt('0xFFFFFFFFFFFFFFFF').toString(2) 会输出完整的 64 位二进制字符串。标准 Number 在超过 9,007,199,254,740,991(2⁵³ - 1)后会丢失精度。我们的进制转换工具内部使用 BigInt,可以处理任意大小的数字。
怎么把十六进制转成二进制?
用 nibble 对照表反向查:每个十六进制位展开成 4 位二进制。0=0000、1=0001、……、A=1010、……、F=1111。举例:十六进制 AF → A(1010)+ F(1111)→ 二进制 10101111。代码里:JavaScript parseInt('AF', 16).toString(2),Python bin(int('AF', 16))[2:]。
八进制怎么直接转十六进制?
两者不能像二进制 ↔ 十六进制那样按位分组直接转,因为 8 和 16 不互为幂次。常规做法是先转为二进制再分组:八进制 755 → 二进制 111 101 101(每位 3 bit)→ 左侧补 0 得 0001 1110 1101 → 十六进制 1ED。或者更简单:用 parseInt('755', 8).toString(16) 一步完成。
Python 2 的 0177 和 Python 3 的 0o177 有什么区别?
Python 2 允许两种写法:0177 和 0o177,都表示八进制 127。Python 3 只接受 0o177,裸前缀 0 会报 SyntaxError。这是因为 Python 2 里 0 前缀继承了 C 的语法(容易让 0789 之类的错误被误解为八进制),Python 3 为避免歧义强制显式前缀。迁移老代码时,搜索 \b0[0-7]+\b 模式可以定位所有隐式八进制字面量。