上个月排查一台上报告警的工控机。日志里那行写的是 alarm=0x0E12,我扫了一眼,把 E12 当十进制读成了"三千六百一十二",又拿文档里的位定义表从 bit0 数到 bit12,对上了一个根本不存在的项目,围着错误的方向查了大半天。后来把 0x0E12 展开成 0000 1110 0001 0010才看清:置 1 的是 bit1、bit4、bit9、bit10、bit11,加起来是 2+16+512+1024+2048 = 3602,跟"3612"差的就是我把十六进制的 1 当成十位那一格。值从来没变,变的只是写法。
一、进制只是同一个数换一种写法
一个数值确定之后,用几个数码去书写它是可以选的。基数决定了每一位能放几个符号、以及进一位乘几。
| 进制 | 基数 | 数码 | 代码里的前缀 | 你会在哪见到 |
|---|---|---|---|---|
| 二进制 | 2 | 0 1 | 0b | 位运算、寄存器、掩码 |
| 八进制 | 8 | 0-7 | 0o(老写法是前导 0) | Linux 权限 chmod 755 |
| 十进制 | 10 | 0-9 | 无 | 日常、日志里的计数值 |
| 十六进制 | 16 | 0-9 A-F | 0x | 颜色、内存地址、协议字节流 |
十六进制之所以在工程里到处是,是因为它和二进制是 4 位一组的整倍数关系:1 个十六进制位正好压住 4 个二进制位,人看一眼 0xFF 就知道那是 8 位全 1。
二、手算只需要三招
- 十进制转别的:反复除以基数取余数,余数倒着排。以 3756 转十六进制为例:3756 ÷ 16 = 234 余 12(C),234 ÷ 16 = 14 余 10(A),14 ÷ 16 = 0 余 14(E),倒排得
0xEAC。 - 别的转十进制:按位权展开。EAC = 14×256 + 10×16 + 12 = 3756,能回到原值说明上一步没写错。
- 二进制 ↔ 十六进制:从右往左每 4 位切一刀,逐组查表。3756 的二进制是
1110 1010 1100,三组分别是 E、A、C。
顺便验证一个不常见的:12345678 用 36 进制(0-9 加 A-Z)写出来是 7CLZI,按权展开 7×36⁴ + 12×36³ + 21×36² + 35×36 + 18 = 11757312 + 559872 + 27216 + 1260 + 18 = 12345678,正好还原。短链、订单后缀那类"字母数字混合码"基本都是 36 进制。
三、常碰到的那几个数,记住比现场换算快
| 十进制 | 二进制 | 十六进制 | 八进制 | 典型出处 |
|---|---|---|---|---|
| 255 | 1111 1111 | FF | 377 | RGB 单通道上限、8 位全 1 |
| 420 | 110100100 | 1A4 | 644 | chmod 644 的真实数值 |
| 493 | 111101101 | 1ED | 755 | chmod 755 的真实数值 |
| 4096 | 1 0000 0000 0000 0000 | 1000 | 10000 | 一页内存的字节数 |
| 65535 | 16 个 1 | FFFF | 177777 | 端口号上限、无符号 16 位 |
| 16777215 | 24 个 1 | FFFFFF | 77777777 | 24 位色的白 #FFFFFF |
| 2147483647 | 31 个 1 | 7FFFFFFF | 17777777777 | 32 位有符号整数上限,接口溢出的报错值 |
| 4294967295 | 32 个 1 | FFFFFFFF | 37777777777 | 无符号 32 位、IPv4 广播地址 |
八进制那一列有个实用结论:755 和 493 是同一个权限的两种写法。配置文件里存哪个都行,怕的是混着用——我看到过同一段脚本一处写 755、一处写 493,diff 里像改了两遍。
四、我现在实际怎么处理
- 先把日志里的原始写法照抄下来,保留
0x、0b前缀,不要凭记忆重敲,E12 那种事故一半来自手抄。 - 数值本身我会用进制转换器算:选好源进制,二、八、十、十六的结果同屏给出,省掉我一边按计算器一边对齐位数。位掩码要逐位勾的时候,我盯的是那个二进制输出,不是十六进制。
- 字节流要按字符看的时候用它另一个方向:文本转成每个字符的进制编码,
A1会得到41 31,分隔符可选,抓包时对照协议表逐字节核。 - 颜色那种六位十六进制我不再手拆。以前把
#FF8A00拆成三段再除 255,经常漏掉进位,直接用颜色转换把 HEX 和 RGB、HSL 对齐更省事。
如果只是想统一文本里的全角半角或看 ASCII 值,文本编码转换更对口,它管的是字符层面,不是数值进制。
五、容易踩的三处
前缀和默认基数不一致。JavaScript 里 parseInt('0755') 得到 755,parseInt('0755', 8) 才是 493;而字面量 0755 在严格模式和 ES Module 下直接抛 SyntaxError,八进制必须写 0o755。同一份权限值,三种写法三种结果,跨语言传配置时务必写全基数。
大数会先丢精度再谈进制。双精度浮点只能精确表示到 2⁵³−1 = 9007199254740991,再往上 9007199254740993 会被存成 ...992。这类数换算前得确认工具按 BigInt 走,否则十六进制看着正常、尾数已经错了。
负数没有"标准十六进制"。-1 写成 FFFFFFFF 只在 32 位补码这个前提下成立,换 16 位就是 FFFF。进制转换处理的是数值本身,位宽要自己带上。
六、常见疑问
Q:为什么计算机非要用二进制?
物理上两个状态最稳(高电平低电平),抗噪和实现成本都最低。八进制、十六进制不是给机器用的,是把几十个 01 压缩成人眼能扫的写法。
Q:十六进制小数怎么转?
整数除基取余,小数是乘基取整:0.5 × 16 = 8.0,所以 0.5 写成 0.8。但 0.1 在任何以 2 为基的进制下都是无限循环的,这正是浮点误差的来源之一,别指望换一种进制能消掉它。
Q:0xFF 和字符串 "FF" 有区别吗?
有。前者是数值字面量,后者是文本,要显式按 16 进制解析才能得到 255。日志、JSON、配置文件里的"十六进制"几乎都是字符串形态,这就是很多换算错位的起点。
Q:为什么最多到 36 进制?
36 刚好是 10 个数字加 26 个字母。再往上要引入大小写区分(62 进制)或其他符号,可读性和不区分大小写的场景都会出问题。
Q:位掩码怎么核对有没有多勾一位?
把合计值转回二进制,逐位和文档表对齐;两边位序(从 0 还是从 1 起数)确认一次。我做的是把 3602 展开成 0000 1110 0001 0010 对着表打勾,比反向把位值加起来更容易发现多出的那一位。
那台上报 0x0E12 的机器最后确认是编码器异常加电机过温同时触发,跟进制没关系。但它让我改掉一个习惯:凡是带 0x、0o 的值,一律展开成二进制或十进制写在注释里,不靠脑内换算。人的十进制直觉是唯一会出错的那一环。