十六进制完全指南:Base-16 数制详解
学习十六进制(base-16)的工作原理,掌握十六进制与十进制的相互转换、十六进制在计算领域的作用、带符号十六进制与二进制补码、十六进制运算,以及 Python、JavaScript 和 C 的编程示例。
十六进制完全指南:Base-16 数制详解
十六进制在计算领域无处不在。如果你曾见过 #FF5733 这样的网页颜色代码、0x7FFF5FBFF8AC 这样的内存地址,或者密码学哈希值,你就已经接触过十六进制数了。然而,许多开发者将十六进制视为一种神秘的记法,只是机械地复制粘贴,并不真正理解它。
本指南将改变这一现状。读完之后,你将能够准确理解十六进制数制的工作原理,在十六进制与十进制之间自由转换,并明白为什么十六进制是底层计算领域的主流记法。你可以使用我们的免费十六进制转十进制工具,边阅读边对下面的示例进行实操验证。
为什么是十六进制?2 的幂次关联
要理解十六进制为何存在,先从一个简单的问题出发:为什么不在计算中直接使用十进制?
答案在于二进制。计算机以二进制(base-2)运行,数据表示为 0 和 1 的序列。单个字节——计算机内存的基本单位——是 8 个二进制位(比特)。字节值 11010110 对计算机来说完全精确,但对人类来说它又长、又容易出错、又枯燥难读。
十进制(base-10)对人类来说是自然的,但它与二进制并不整齐对齐。在二进制和十进制之间转换需要除法和乘法运算,这会掩盖底层的位模式。
十六进制(base-16)解决了这个问题,因为 16 是 2 的幂次:16 = 2^4。这意味着每个十六进制数字恰好代表 4 个二进制位,两个十六进制数字恰好代表一个字节,对齐完美:
| 二进制(8 位) | 十六进制(2 位) | 十进制 |
|---|---|---|
| 0000 0000 | 00 | 0 |
| 0111 1111 | 7F | 127 |
| 1000 0000 | 80 | 128 |
| 1111 1111 | FF | 255 |
字节值 11010110 在十六进制中只需 D6 两个字符,直接映射到底层位,无需任何运算。这正是十六进制成为计算领域首选记法的原因。
十六进制数字:0-9 与 A-F
十六进制使用十六个符号来表示零到十五的值:
| 十六进制数字 | 十进制值 | 二进制 |
|---|---|---|
| 0 | 0 | 0000 |
| 1 | 1 | 0001 |
| 2 | 2 | 0010 |
| 3 | 3 | 0011 |
| 4 | 4 | 0100 |
| 5 | 5 | 0101 |
| 6 | 6 | 0110 |
| 7 | 7 | 0111 |
| 8 | 8 | 1000 |
| 9 | 9 | 1001 |
| A | 10 | 1010 |
| B | 11 | 1011 |
| C | 12 | 1100 |
| D | 13 | 1101 |
| E | 14 | 1110 |
| F | 15 | 1111 |
字母 A 到 F 在十个十进制数字之外,用于表示 10 到 15 的值。大写(A-F)和小写(a-f)在几乎所有场合都被接受,但大写是约定俗成的标准。
常用十六进制前缀与记法
不同的编程语言和场景使用不同的记法来标识十六进制数:
0x— C、C++、Java、Python、JavaScript:0xFF#— CSS 颜色代码:#FF5733$— 6502/68000 汇编:$FFh后缀 — Intel 汇编:FFhU+— Unicode 码点:U+0041\x— 字符串中的十六进制转义:\x41(字母 A)
前缀或记法不会改变数值——它只是告诉解析器或读者将这些数字解释为十六进制。
如何将十六进制转换为十进制
核心技术是位值表示法——与所有数制的工作原理相同,只是以 16 为基数而非 10。
公式
对于一个由数字 d(n-1)、d(n-2)、……、d(1)、d(0) 组成的十六进制数,其十进制值为:
十进制 = d(n-1) × 16^(n-1) + d(n-2) × 16^(n-2) + … + d(1) × 16^1 + d(0) × 16^0
每个十六进制数字乘以 16 的对应位次幂(从右向左,从 0 开始计数),再将所有乘积相加。
16 的幂次参考表
记住前几个 16 的幂次,可以大幅加快心算转换速度:
| 幂次 | 值 |
|---|---|
| 16^0 | 1 |
| 16^1 | 16 |
| 16^2 | 256 |
| 16^3 | 4,096 |
| 16^4 | 65,536 |
| 16^5 | 1,048,576 |
| 16^6 | 16,777,216 |
| 16^7 | 268,435,456 |
| 16^8 | 4,294,967,296 |
示例一:将 2F 转换为十进制
十六进制输入: 2F
- 数字 2 位于第 1 位:2 × 16^1 = 2 × 16 = 32
- 数字 F(15)位于第 0 位:15 × 16^0 = 15 × 1 = 15
- 求和:32 + 15 = 47
结果: 2F(十六进制)= 47(十进制)
示例二:将 1A3F 转换为十进制
十六进制输入: 1A3F
- 数字 1 位于第 3 位:1 × 16^3 = 1 × 4,096 = 4,096
- 数字 A(10)位于第 2 位:10 × 16^2 = 10 × 256 = 2,560
- 数字 3 位于第 1 位:3 × 16^1 = 3 × 16 = 48
- 数字 F(15)位于第 0 位:15 × 16^0 = 15 × 1 = 15
- 求和:4,096 + 2,560 + 48 + 15 = 6,719
结果: 1A3F(十六进制)= 6,719(十进制)
示例三:将 DEADBEEF 转换为十进制
十六进制输入: DEADBEEF(一个著名的「hexspeak」值,常作为调试中的幻数使用)
- D(13)× 16^7 = 13 × 268,435,456 = 3,489,660,928
- E(14)× 16^6 = 14 × 16,777,216 = 234,881,024
- A(10)× 16^5 = 10 × 1,048,576 = 10,485,760
- D(13)× 16^4 = 13 × 65,536 = 851,968
- B(11)× 16^3 = 11 × 4,096 = 45,056
- E(14)× 16^2 = 14 × 256 = 3,584
- E(14)× 16^1 = 14 × 16 = 224
- F(15)× 16^0 = 15 × 1 = 15
求和:3,489,660,928 + 234,881,024 + 10,485,760 + 851,968 + 45,056 + 3,584 + 224 + 15 = 3,735,928,559
这是一个 32 位无符号整数,调试工具常用它来标记未初始化内存。
如何将十进制转换为十六进制
反向过程使用反复除以 16 的方法:
- 将十进制数除以 16
- 记录余数(作为一个十六进制数字)
- 用商替换当前数
- 重复上述步骤,直到商为 0
- 从下往上读取十六进制数字(最后的余数在最前面)
示例:将 6,719 转换为十六进制
| 步骤 | 运算 | 商 | 余数 | 十六进制数字 |
|---|---|---|---|---|
| 1 | 6719 / 16 | 419 | 15 | F |
| 2 | 419 / 16 | 26 | 3 | 3 |
| 3 | 26 / 16 | 1 | 10 | A |
| 4 | 1 / 16 | 0 | 1 | 1 |
从下往上读:1A3F
验证:1A3F(十六进制)= 1×4096 + 10×256 + 3×16 + 15×1 = 4096 + 2560 + 48 + 15 = 6,719。正确。
十六进制在计算领域的应用
内存地址
调试程序时,内存地址以十六进制显示:
0x7FFF5FBFF8AC
0x00400000(Linux 上 .text 节的典型起始地址)
0xDEADBEEF(未初始化内存的调试标记)
使用十六进制是因为内存是按字节寻址的,两个十六进制数字映射到一个字节。一个 64 位地址如 0x7FFF5FBFF8AC 是 12 个十六进制数字——远比它所表示的 48 个二进制数字更易读。
网页颜色代码
CSS 十六进制颜色编码红、绿、蓝三种强度值:
| 十六进制代码 | R(十进制) | G(十进制) | B(十进制) | 颜色 |
|---|---|---|---|---|
| #000000 | 0 | 0 | 0 | 黑色 |
| #FFFFFF | 255 | 255 | 255 | 白色 |
| #FF0000 | 255 | 0 | 0 | 红色 |
| #00FF00 | 0 | 255 | 0 | 绿色 |
| #0000FF | 0 | 0 | 255 | 蓝色 |
| #FF5733 | 255 | 87 | 51 | 橙红色 |
| #3A7BD5 | 58 | 123 | 213 | 钢蓝色 |
每对十六进制数字代表一个字节(十进制 0-255),你可以使用我们的十六进制转十进制工具转换这些值。完整的 RGB 分解,可以使用我们的十六进制转 RGB 工具。
MAC 地址
网络接口地址由六对十六进制字节组成,以冒号或连字符分隔:
00:1A:2B:3C:4D:5E
前三个字节(00:1A
)标识制造商(OUI),后三个字节(3C:4D)是设备专属标识符。每对字节对应一个十六进制字节,转换为十进制值范围是 0-255。Unicode 码点
每个 Unicode 字符都有一个以十六进制写成的码点:
| 码点 | 十六进制值 | 十进制 | 字符 |
|---|---|---|---|
| U+0041 | 41 | 65 | A |
| U+0048 | 48 | 72 | H |
| U+00E9 | E9 | 233 | é(带重音) |
| U+4E16 | 4E16 | 19,990 | 世(中文「世界」) |
| U+1F600 | 1F600 | 128,512 | 笑脸表情符号 |
十六进制记法之所以被使用,是因为它与底层字节编码(UTF-8、UTF-16)对齐,并且对整个 Unicode 范围(0 到 10FFFF)来说比十进制更紧凑。
每位开发者都应了解的常见十六进制值
某些十六进制值在编程和系统管理中出现频率极高,能够立刻认出它们可以节省大量时间。
字节边界
| 十六进制 | 十进制 | 含义 |
|---|---|---|
| 0x00 | 0 | 空字节,C 中的字符串终止符 |
| 0x0A | 10 | 换行符(LF),Unix 换行 |
| 0x0D | 13 | 回车符(CR) |
| 0x20 | 32 | ASCII 空格字符 |
| 0x7F | 127 | 有符号 8 位整数最大值,DEL 字符 |
| 0x80 | 128 | 有符号 8 位最小值(二进制补码) |
| 0xFF | 255 | 无符号 8 位最大值(1 字节) |
| 0xFFFF | 65,535 | 无符号 16 位最大值 |
| 0xFFFFFFFF | 4,294,967,295 | 无符号 32 位最大值 |
ASCII 字符十六进制范围
| 十六进制范围 | 十进制范围 | 字符 |
|---|---|---|
| 30-39 | 48-57 | 数字 '0' 到 '9' |
| 41-5A | 65-90 | 大写字母 'A' 到 'Z' |
| 61-7A | 97-122 | 小写字母 'a' 到 'z' |
了解大写字母 A 从 0x41(65)开始,小写字母 a 从 0x61(97)开始——两者相差恰好 0x20(32)——在低级代码中实现大小写转换时非常有用。
调试与幻数
程序员使用可识别的十六进制模式来标记特殊的内存状态:
| 十六进制 | 十进制 | 用途 |
|---|---|---|
| 0xDEADBEEF | 3,735,928,559 | 未初始化内存标记(IBM) |
| 0xCAFEBABE | 3,405,691,582 | Java 类文件幻数 |
| 0xBAADF00D | 3,131,961,357 | Windows LocalAlloc 未初始化堆 |
| 0xFEEDFACE | 4,277,009,102 | Mach-O 二进制头(macOS) |
| 0xDEADC0DE | 3,735,929,054 | OpenSolaris 未初始化缓冲区标记 |
这些「hexspeak」值之所以被选中,是因为它们在内存转储中醒目易辨,同时只使用十六进制有效字母(A-F)。
带符号十六进制与二进制补码
在大多数现代计算机中,负整数使用二进制补码编码存储。理解这一点对于解读内存转储、寄存器值和网络协议中的有符号十六进制值至关重要。
二进制补码的工作原理
对于 N 位有符号整数:
- 如果最高有效位(MSB)为 0,则数为正数(或零)
- 如果 MSB 为 1,则数为负数
从 MSB 被置位的十六进制数中求负值:
- 转换为二进制
- 对所有位取反(0 变 1,1 变 0)
- 加 1
- 结果为绝对值,在前面加负号
8 位有符号十六进制示例
| 十六进制 | 二进制 | 无符号值 | 有符号值(8 位) |
|---|---|---|---|
| 00 | 00000000 | 0 | 0 |
| 7F | 01111111 | 127 | +127(最大正值) |
| 80 | 10000000 | 128 | -128(最小负值) |
| FF | 11111111 | 255 | -1 |
| FE | 11111110 | 254 | -2 |
| 81 | 10000001 | 129 | -127 |
示例:0x9C 作为有符号 8 位整数是多少?
- 二进制:10011100
- MSB 为 1,因此是负数
- 取反:01100011
- 加 1:01100100 = 十进制 100
- 结果:-100
因此,0x9C 作为无符号字节是 156,作为有符号字节则是 -100。解释完全取决于上下文。
16 位和 32 位有符号值
同样的原理适用于更宽的整数:
| 十六进制 | 无符号值 | 有符号值(32 位) |
|---|---|---|
| 00000000 | 0 | 0 |
| 7FFFFFFF | 2,147,483,647 | +2,147,483,647(最大) |
| 80000000 | 2,147,483,648 | -2,147,483,648(最小) |
| FFFFFFFF | 4,294,967,295 | -1 |
| FFFFFFFE | 4,294,967,294 | -2 |
十六进制运算
虽然大多数人使用计算器进行十六进制运算,但理解基本原理对调试和心算很有帮助。
十六进制加法
十六进制加法与十进制加法规则相同,只是进位阈值为 16 而非 10:
3A
+ 1F
----
- 右列:A(10)+ F(15)= 25。因为 25 ≥ 16,写 25 - 16 = 9,进位 1
- 左列:3 + 1 + 1(进位)= 5
结果:3A + 1F = 59
验证:58 + 31 = 89(十进制)。59(十六进制)= 5×16 + 9 = 89。正确。
十六进制减法
FF
- 3A
----
- 右列:F(15)- A(10)= 5
- 左列:F(15)- 3 = C(12)
结果:FF - 3A = C5
验证:255 - 58 = 197。C5(十六进制)= 12×16 + 5 = 197。正确。
快速心算技巧
- F 加 1 等于 10(十六进制),而不是 16——记住你在十六进制中运算
- FF + 1 = 100(十六进制)= 256(十进制)
- 10(十六进制)减 1 等于 F
- 将某个十六进制数字翻倍:将其十进制值翻倍,若 ≥ 16 则转换回十六进制
编程语言中的十六进制转十进制
Python
Python 的 int() 函数是解析十六进制字符串的标准方式:
# 十六进制字符串转十进制
result = int("FF", 16) # 255
result = int("1A3F", 16) # 6719
# 十六进制字面量
value = 0xFF # 255
# 十进制转十六进制
hex(255) # '0xff'
format(255, 'X') # 'FF'
format(255, '04X') # '00FF'(补零填充)
# 带 0x 前缀解析
int("0xFF", 16) # 255
int("0xFF", 0) # 255(自动检测进制)
JavaScript
JavaScript 使用 parseInt() 解析十六进制字符串,0x 前缀用于字面量:
// 十六进制字符串转十进制
parseInt("FF", 16) // 255
parseInt("1A3F", 16) // 6719
// 十六进制字面量
const value = 0xFF; // 255
// 十进制转十六进制
(255).toString(16) // 'ff'
(255).toString(16).toUpperCase() // 'FF'
// 对超大值使用 BigInt
BigInt("0xDEADBEEFDEADBEEF") // 16045690984833335023n
// Number.parseInt 同样有效
Number.parseInt("FF", 16) // 255
C / C++
C 使用 strtol() 进行字符串转换,并直接支持十六进制字面量:
#include <stdio.h>
#include <stdlib.h>
int main() {
// 字符串转十进制
long value = strtol("FF", NULL, 16); // 255
printf("%ld\n", value);
// 十六进制字面量
int x = 0xFF; // 255
printf("%d\n", x); // 十进制:255
printf("%X\n", x); // 十六进制:FF
printf("%08X\n", x); // 补零填充:000000FF
// 无符号 32 位
unsigned long big = strtoul("DEADBEEF", NULL, 16);
printf("%lu\n", big); // 3735928559
return 0;
}
常见问题
如何将十六进制转换为十进制?
将每个十六进制数字乘以其对应位次的 16 的幂次,然后将所有乘积相加。例如,十六进制 2F = 2×16 + 15×1 = 32 + 15 = 47。乘法前先将字母 A-F 替换为对应的十进制值(A=10、B=11、C=12、D=13、E=14、F=15)。
0xFF 的十进制是多少?
0xFF 在十进制中等于 255。F(15)× 16 + F(15)× 1 = 240 + 15 = 255。这是无符号 8 位字节的最大值(二进制 11111111),在编程中常用作位掩码。
0x 前缀是什么意思?
0x 前缀是 C、Java、Python、JavaScript 等众多语言中使用的记法约定,表示后续数字为十六进制。它不改变数值——0xFF 和 FF 表示同一个数(十进制 255)。
如何将十进制转换为十六进制?
反复将十进制数除以 16,记录每次的余数。从下往上读取余数,即构成十六进制数。余数为 10-15 时使用字母 A-F。例如,255 / 16 = 15 余 15,然后 15 / 16 = 0 余 15。从下往上读:FF。
十六进制和十进制有什么区别?
十六进制是 base-16(数字 0-9 和 A-F),十进制是 base-10(仅数字 0-9)。十六进制在计算中更受青睐,因为每个十六进制数字恰好映射到 4 个二进制位,是二进制数据的紧凑表示。十进制是人类的标准计数系统。
如何将带符号的十六进制转换为十进制?
对于带符号十六进制值(二进制补码),检查最高有效位。如果被置位(8 位值中最左边位置的十六进制数字为 8-F),则数为负数。求绝对值:对所有位取反后加 1。例如,0xFF 作为有符号 8 位整数为 -1(11111111 取反得 00000000,加 1 得 1,取负得 -1)。
十六进制在哪些地方使用?
十六进制用于内存地址、网页颜色代码(#FF5733)、MAC 地址、Unicode 码点(U+0041)、密码学哈希值(SHA-256)、汇编语言、IPv6 地址,以及一切需要用紧凑易读记法表示二进制数据的场合。
为什么十六进制优于八进制?
十六进制与字节边界对齐:2 个十六进制数字 = 1 个字节(8 位)。八进制(base-8)使用 3 位分组,无法与 8 位字节整齐对齐。一个字节需要 2.67 个八进制数字,但恰好需要 2 个十六进制数字。这就是为什么十六进制在现代计算中基本取代了八进制,尽管八进制在 Unix 文件权限中依然存在(chmod 755)。
如何进行十六进制运算?
十六进制运算遵循与十进制运算相同的规则,只是进位/借位阈值为 16 而非 10。加法时,如果某列之和超过 15(F),则减去 16 并向下一列进位 1。减法时,如果某列之差为负,则加 16 并从下一列借位 1。
十六进制可以有小数点吗?
可以,尽管很少见。十六进制 1.8 等于十进制 1 + 8/16 = 1.5。某些编程场景(C99 的 %a 格式、IEEE 754 十六进制浮点记法)使用十六进制浮点,如 0x1.8p3(表示 1.5 × 2^3 = 12.0)。实际上,大多数十六进制转十进制转换处理的是整数。
十六进制与其他数制的比较
了解十六进制与其他数制的比较,有助于明确何时使用哪种数制:
| 属性 | 二进制(Base-2) | 八进制(Base-8) | 十进制(Base-10) | 十六进制(Base-16) |
|---|---|---|---|---|
| 使用的数字 | 0-1 | 0-7 | 0-9 | 0-9、A-F |
| 每位二进制位数 | 1 | 3 | ~3.32 | 4 |
| 字节对齐 | 完美(8 位) | 差(2.67 位) | 无(可变) | 完美(2 位) |
| 主要用途 | 硬件逻辑 | Unix 权限 | 人类数学 | 计算记法 |
在计算场景中,十六进制凭借完美的字节对齐占据优势。八进制在 Unix 文件权限(chmod 755 = rwxr-xr-x)中依然存在,因为 3 位分组与读/写/执行权限模型完美匹配。十进制仍然是面向人类的值(如价格、计数和度量)的标准。
如需在这些数制之间转换,可以使用我们的二进制转十进制工具或二进制转八进制工具,配合十六进制转十进制工具一起使用。
小结
十六进制是人类可读记法与计算机二进制现实之间的桥梁。它的力量来自一个简单的数学事实:16 = 2^4,因此每个十六进制数字恰好映射到 4 个二进制位。这种对齐使十六进制成为紧凑表达二进制数据的自然方式。
本指南的核心要点:
- 十六进制转十进制:将每位数字乘以其对应的 16 的幂次,再将乘积相加
- 十进制转十六进制:反复除以 16,从下往上收集余数
- 两个十六进制数字 = 一个字节:FF = 255,无符号字节的最大值
- 带符号十六进制使用二进制补码——同样的 8 位可以表示 255(无符号)或 -1(有符号)
- 十六进制运算遵循标准规则,进位阈值为 16
- 编程:Python
int("FF", 16)、JavaScriptparseInt("FF", 16)、Cstrtol("FF", NULL, 16)
准备好转换了吗?使用我们的免费十六进制转十进制工具,即时将任意十六进制值转换为十进制,并获得逐步位值表示法分解。你也可以探索我们的十六进制转二进制工具和十六进制转文本工具,进行相关转换。