一句话概括
计算机只认识 0 和 1,但人类需要十进制,程序员偏爱十六进制,操作系统权限爱用八进制——进制转换,本质上是同一个数字换了几种"方言"。看完这篇文章,你会发现换算规则简单到令人发指。
一、为什么程序员绕不开进制转换?
先看几个你一定遇到过的场景:
- 前端调试时,颜色代码
#FF5733里的每一位到底是什么意思? - Linux 下
chmod 755的755是怎么算出来的? - 网络编程里子网掩码
255.255.255.0和/24是什么关系? - 内存地址
0x7FFE1234ABCD里的0x是什么意思,后面那串又该怎么读?
如果你说不清这些问题,说明进制转换这一关还没真正打通。别担心,这篇文章会用最直观的方式,把二、八、十、十六进制之间的转换彻底讲透。
二、先搞懂"进制"到底是什么
2.1 一句话定义
进制,就是"逢几进一"的计数规则。
| 进制 | 英文 | 逢几进一 | 可用数字 | 常见前缀/后缀 |
|---|---|---|---|---|
| 二进制 | Binary | 逢 2 进 1 | 0、1 | 0b(如0b1010) |
| 八进制 | Octal | 逢 8 进 1 | 0~7 | 0(如012)或0o |
| 十进制 | Decimal | 逢 10 进 1 | 0~9 | 无(日常使用) |
| 十六进制 | Hexadecimal | 逢 16 进 1 | 09、AF | 0x(如0x1A) |
2.2 核心公式:位权展开
任何进制的数字,本质都是"每一位的数字 × 该位的权重"之和。
以十进制1234为例,我们从小就在用位权展开,只是没意识到:
1234 = 1×10³ + 2×10² + 3×10¹ + 4×10⁰ = 1000 + 200 + 30 + 4这个规律对所有进制通用,只是把"10"换成对应的进制基数(Base):
任意 N 进制数字的值 = Σ (每一位数字 × N的位权次方)记住这一个公式,你就掌握了所有"转十进制"的钥匙。
三、任意进制 → 十进制:位权展开法
3.1 二进制 → 十进制
以二进制1101为例:
从右往左,位权依次是 2⁰、2¹、2²、2³ 1 1 0 1 ↓ ↓ ↓ ↓ 2³=8 2²=4 2¹=2 2⁰=1 1101(2) = 1×8 + 1×4 + 0×2 + 1×1 = 13(10)3.2 八进制 → 十进制
以八进制752为例:
752(8) = 7×8² + 5×8¹ + 2×8⁰ = 7×64 + 5×8 + 2×1 = 448 + 40 + 2 = 490(10)3.3 十六进制 → 十进制
十六进制里A~F分别代表10~15,这是唯一需要额外记忆的点。以1A3为例:
1A3(16) = 1×16² + 10×16¹ + 3×16⁰ = 256 + 160 + 3 = 419(10)| 十六进制字符 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|
| 十进制值 | 10 | 11 | 12 | 13 | 14 | 15 |
小结:无论几进制转十进制,套路都一样——从右往左编号 0、1、2……,每一位乘以"基数的该次方",再全部相加。
四、十进制 → 任意进制:除基取余法
这是很多人容易搞混方向的地方,记住口诀:
除以谁,就是转成谁的进制;余数从下往上读,就是答案。
4.1 十进制 → 二进制(以 89 为例)
89 ÷ 2 = 44 ...... 余 1 ↑ 44 ÷ 2 = 22 ...... 余 0 ↑ 22 ÷ 2 = 11 ...... 余 0 ↑ 11 ÷ 2 = 5 ...... 余 1 ↑ 5 ÷ 2 = 2 ...... 余 1 ↑ 2 ÷ 2 = 1 ...... 余 0 ↑ 1 ÷ 2 = 0 ...... 余 1 (最后一步,商为 0 时停止) 从下往上读余数:1011001 89(10) = 1011001(2)验算一下(用第三节的位权展开法反推):1011001 = 64+16+8+1 = 89✅ 完全正确。
4.2 十进制 → 八进制(以 490 为例)
490 ÷ 8 = 61 ...... 余 2 ↑ 61 ÷ 8 = 7 ...... 余 5 ↑ 7 ÷ 8 = 0 ...... 余 7 ↑ 从下往上读:752 490(10) = 752(8)4.3 十进制 → 十六进制(以 419 为例)
419 ÷ 16 = 26 ...... 余 3 ↑ 26 ÷ 16 = 1 ...... 余 10 → A ↑ 1 ÷ 16 = 0 ...... 余 1 ↑ 从下往上读:1A3 419(10) = 1A3(16)口诀总结:除基取余,余数逆序,不够拿字母(十六进制专属)。
五、划重点!二、八、十六进制之间的"闪电转换法"
如果每次都要绕道十进制,效率太低。而二、八、十六进制之间,其实有一条几乎不用计算的捷径——因为它们的基数都是 2 的幂次:
2¹ = 2 (二进制) 2³ = 8 (八进制) 2⁴ = 16 (十六进制)核心原理:3 位二进制 ⇔ 1 位八进制;4 位二进制 ⇔ 1 位十六进制。
5.1 二进制 ↔ 八进制:三位一组
二进制转八进制:从右往左,每 3 位分一组,不足补 0,再分别转十进制。
以1011001为例:
从右往左每 3 位分组(不足在左边补0): 001 011 001 ↓ ↓ ↓ 1 3 1 1011001(2) = 131(8)八进制转二进制:反过来,每一位八进制数字,展开成 3 位二进制。
以752为例:
7 → 111 5 → 101 2 → 010 拼接:111 101 010 752(8) = 111101010(2)5.2 二进制 ↔ 十六进制:四位一组
二进制转十六进制:从右往左,每 4 位分一组,不足补 0。
以1011001为例:
从右往左每 4 位分组: 0101 1001 ↓ ↓ 5 9 1011001(2) = 59(16)十六进制转二进制:每一位十六进制数字,展开成 4 位二进制。
以1A3为例:
1 → 0001 A → 1010 3 → 0011 拼接:0001 1010 0011 1A3(16) = 000110100011(2) → 去掉前导0 → 110100011(2)5.3 对照速查表(强烈建议背下来)
| 十进制 | 二进制 | 八进制 | 十六进制 |
|---|---|---|---|
| 0 | 0000 | 0 | 0 |
| 1 | 0001 | 1 | 1 |
| 2 | 0010 | 2 | 2 |
| 3 | 0011 | 3 | 3 |
| 4 | 0100 | 4 | 4 |
| 5 | 0101 | 5 | 5 |
| 6 | 0110 | 6 | 6 |
| 7 | 0111 | 7 | 7 |
| 8 | 1000 | 10 | 8 |
| 9 | 1001 | 11 | 9 |
| 10 | 1010 | 12 | A |
| 11 | 1011 | 13 | B |
| 12 | 1100 | 14 | C |
| 13 | 1101 | 15 | D |
| 14 | 1110 | 16 | E |
| 15 | 1111 | 17 | F |
把这张表刻进脑子里,你看一眼十六进制数字,就能秒转二进制——这正是十六进制在计算机里大行其道的原因:它是二进制的"压缩包",4 位变 1 位,还不丢信息。
六、实战演示:写一个"万能进制转换器"
光讲手算不过瘾,我们用代码把整套逻辑跑一遍——分别演示"任意进制转十进制"和"十进制转任意进制"两个方向,并验证第五节的闪电转换法。
6.1 C# 实现(.NET 内置方法 + 手写原理两个版本)
usingSystem;classNumberSystemConverter{staticvoidMain(){Console.WriteLine("========== 方式一:调用 .NET 内置方法 ==========\n");BuiltInDemo();Console.WriteLine("\n========== 方式二:手写位权展开 / 除基取余 ==========\n");ManualDemo();}// ---------- 方式一:内置方法,工程中直接用这个 ----------staticvoidBuiltInDemo(){intnumber=89;// 十进制 -> 其他进制:Convert.ToString(数字, 基数)Console.WriteLine($"{number}的二进制 ={Convert.ToString(number,2)}");Console.WriteLine($"{number}的八进制 ={Convert.ToString(number,8)}");Console.WriteLine($"{number}的十六进制 ={Convert.ToString(number,16).ToUpper()}");// 其他进制 -> 十进制:Convert.ToInt32(字符串, 基数)stringbinStr="1011001";stringhexStr="1A3";Console.WriteLine($"\n二进制{binStr}转十进制 ={Convert.ToInt32(binStr,2)}");Console.WriteLine($"十六进制{hexStr}转十进制 ={Convert.ToInt32(hexStr,16)}");// 十六进制格式化输出的另一种写法Console.WriteLine($"\n{number:X}(大写) /{number:x}(小写)");// 格式化字符串}// ---------- 方式二:不依赖内置方法,手写还原算法原理 ----------staticvoidManualDemo(){intdecimalNum=419;Console.WriteLine($"手写实现:{decimalNum}(10) 转二进制、八进制、十六进制\n");Console.WriteLine($"二进制 ={ToBaseString(decimalNum,2)}");Console.WriteLine($"八进制 ={ToBaseString(decimalNum,8)}");Console.WriteLine($"十六进制 ={ToBaseString(decimalNum,16)}");Console.WriteLine();stringhex="1A3";Console.WriteLine($"手写实现:十六进制{hex}转十进制 ={FromBaseString(hex,16)}");}/// <summary>/// 除基取余法:十进制 -> 任意进制(原理演示,非内置方法)/// </summary>staticstringToBaseString(intnumber,inttargetBase){if(number==0)return"0";conststringdigits="0123456789ABCDEF";stringresult="";while(number>0){intremainder=number%targetBase;// 取余result=digits[remainder]+result;// 余数逆序拼接number/=targetBase;// 继续除}returnresult;}/// <summary>/// 位权展开法:任意进制字符串 -> 十进制(原理演示,非内置方法)/// </summary>staticintFromBaseString(stringnumber,intfromBase){conststringdigits="0123456789ABCDEF";intresult=0;foreach(charcinnumber){intdigitValue=digits.IndexOf(char.ToUpper(c));result=result*fromBase+digitValue;// 秦九韶算法,等价于位权展开}returnresult;}}6.2 运行结果
========== 方式一:调用 .NET 内置方法 ========== 89 的二进制 = 1011001 89 的八进制 = 131 89 的十六进制 = 59 二进制 1011001 转十进制 = 89 十六进制 1A3 转十进制 = 419 59 (大写) / 59 (小写) ========== 方式二:手写位权展开 / 除基取余 ========== 手写实现:419(10) 转二进制、八进制、十六进制 二进制 = 110100011 八进制 = 643 十六进制 = 1A3 手写实现:十六进制 1A3 转十进制 = 4196.3 代码里藏了一个"高手技巧":秦九韶算法
FromBaseString方法里的这一行:
result=result*fromBase+digitValue;这其实是位权展开公式的另一种写法,比如四位数d3 d2 d1 d0:
标准展开:d3×N³ + d2×N² + d1×N¹ + d0×N⁰ 秦九韶算法(霍纳法则):((d3×N + d2)×N + d1)×N + d0两者结果完全相同,但秦九韶算法只需要循环里做乘加,不需要单独计算幂次,代码更简洁,性能也更优——这是位权展开公式在工程中最常见的实现方式,建议直接记住这个写法。
七、进制转换在真实开发中的 4 个高频场景
场景 1:颜色代码(十六进制)
#FF5733 └┬┘└┬┘└┬┘ R G B FF = 255(红色分量拉满) 57 = 87 33 = 51十六进制正好两位表示 0~255,这就是颜色码普遍用十六进制而不是十进制的原因——更紧凑,且和字节(8 bit = 2 位十六进制)完美对齐。
场景 2:Linux 文件权限(八进制)
chmod 755 file.txt 7 = 111 → rwx(所有者:读写执行) 5 = 101 → r-x(同组:读执行) 5 = 101 → r-x(其他:读执行)每一位八进制数字恰好对应 3 个权限位(读/写/执行),这正是权限管理偏爱八进制的原因。
场景 3:网络子网掩码
255.255.255.0 = 11111111.11111111.11111111.00000000 = 前 24 位是 1 → 也就是常说的 "/24"把点分十进制换算成二进制,才能真正看懂子网划分的逻辑。
场景 4:内存地址与调试
0x7FFE1234ABCD内存地址普遍用十六进制展示,是因为它能用最短的字符串,精确表示一长串二进制地址——4 位二进制压缩成 1 位十六进制,可读性直接起飞。
八、常见误区与避坑指南
坑 1:分组方向搞反
二进制转八进制/十六进制分组,必须从右往左数,不够位数在左边补 0,而不是从左往右分!
错误示范:10110 从左往右分 3 位 → 101 10(补0)10 → 5, 2 ❌ 正确做法:10110 从右往左分 3 位 → 10 110 → 补0成 010 110 → 2, 6 → 26(8)坑 2:小数部分的进制转换方向也容易搞反
整数部分是"除基取余,从下往上读";小数部分则是"乘基取整,从上往下读",两者方向刚好相反:
0.625(10) 转二进制: 0.625 × 2 = 1.25 → 取整 1 ↓ 0.25 × 2 = 0.5 → 取整 0 ↓ 0.5 × 2 = 1.0 → 取整 1 ↓ (小数部分为0,停止) 从上往下读:0.101 0.625(10) = 0.101(2)坑 3:八进制的"0 前缀"陷阱
在很多语言(包括早期 C 风格语言)中,010会被当成八进制的 8,而不是十进制的 10!C# 中虽然去掉了这个坑(普通字面量不支持0前缀八进制),但读别人代码、写配置文件(如 Unix umask)时一定要留心。
坑 4:十六进制字母大小写不影响数值
1A3和1a3是完全相同的值,但代码规范里通常建议大写字母表示十六进制数字,与十进制数字形成更清晰的视觉区分。
九、一张图总结全部转换路径
位权展开法 ┌─────────────────────────┐ │ ▼ 二进制 ──三位一组──▶ 八进制 十进制 │ ▲ └─四位一组──▶ 十六进制 ────┘ (除基取余法:反向箭头)记住三句口诀,走遍天下都不怕:
- 转十进制 → 位权展开,逢位相乘再相加
- 十进制转任意进制 → 除基取余,余数逆序往上排
- 二进制与八/十六进制互转 → 三位一组变八进制,四位一组变十六进制
十、写在最后
进制转换看似是"数学题",实际上是理解计算机底层逻辑的一把钥匙。当你能秒懂0x1A3、chmod 755、#FF5733背后的数字含义时,你看代码、读文档、排查 bug 的速度都会有质的飞跃。
下次再看到一串陌生的十六进制或八进制数字,别慌——套上今天学的三句口诀,几秒钟就能心算出答案。
📝 课后练习
- 手算:十进制
1000分别转换成二进制、八进制、十六进制,并用位权展开法验算。 - 手算:二进制
11010110直接转换成十六进制(提示:四位一组,不要绕道十进制)。 - 编程实现:在第六节代码的基础上,增加对带小数的十进制数转二进制的支持(提示:整数部分和小数部分要分开处理,方向相反)。
觉得有收获?欢迎点赞收藏,下次调试内存地址或配置文件权限时,不用再掐指心算,秒懂即可下笔 😉