进制转换一文讲透:二进制、八进制、十进制、十六进制互转与实战
很多人以为进制转换是考试完就丢的东西直到第一次在协议抓包里看到FF D8 FF E0第一次用 HxD 打开文件发现开头是89 50 4E 47第一次在嵌入式调试里看到地址0x7FFFFFFF才会意识到十六进制、二进制不是课本概念而是实实在在工作流的一部分。这篇文章我就把二进制、八进制、十进制、十六进制之间的转换一次讲透整数、小数、负数、分组技巧全都覆盖也会把我在实际调试中踩过的坑一并交代。无论你是刚学编程的学生还是想补基础的前端、运维、嵌入式开发这篇文章都值得收藏着当工具看。1. 进制的底层逻辑位置计数法决定了每一位的身价1.1 十进制不是自然规则只是一种习惯十进制之所以叫十进制是因为它用了 0 到 9 这十个符号并且规则是满十进一。这个规则太熟了熟到我们根本不会去思考一个数字比如 217 到底是什么含义。把它拆开看是这样217 2 × 10² 1 × 10¹ 7 × 10⁰也就是2 个百、1 个十、7 个一。这里的 10²、10¹、10⁰ 叫权重每个位置都有自己的身价。这是在所有进制里都通用的核心逻辑任何进制数都是由数字符号 × 当前进制 ^ 位置序号累加出来的位置序号从右往左从 0 开始数。理解了这句话后面所有转换都能推导出来不需要死记任何口诀。1.2 二进制只有 0 和 1 的开关语言二进制只用 0 和 1 两个符号规则是满二进一。它和计算机硬件的契合度完美电压高是 1、电压低是 0开关闭合是 1、断开是 0。二进制是计算机唯一能直接处理的数字系统所有高级语言里的数字、文字、图片、视频最终都要落到二进制。二进制数的每一位权重是 2 的幂。比如二进制11011001展开是1×2⁷ 1×2⁶ 0×2⁵ 1×2⁴ 1×2³ 0×2² 0×2¹ 1×2⁰ 128 64 0 16 8 0 0 1 217注意二进制位从右往左分别是 2⁰、2¹、2²……这是所有进制转换的基石。很多初学者会把 2⁰ 和 2¹ 的位置搞混记住一个规律最右边永远是个位只不过十进制的个位权重是 10⁰1二进制的个位权重是 2⁰1所以判断一个二进制数奇偶性只看最后一位是 0 还是 1 就行。这个技巧在实际排查里非常有用。1.3 八进制和十六进制为什么偏偏是 8 和 16二进制写起来实在太长了一个 1024 要写 11 位。程序员想偷懒就找了两个压缩格式八进制和十六进制。选它们不是随便拍的而是因为 8 2³16 2⁴二进制位刚好可以按 3 位、4 位一组直接映射转换不需要任何计算纯靠眼睛分组就能完成。这一点我在后面8421 法则里详细展开。三个进制的符号表如下进制基数允许的数字符号常见前缀示例二进制20, 10b0b11011001八进制80 ~ 700331十进制100 ~ 9无217十六进制160 ~ 9, A ~ F0x0xD9这里有个很容易被新人忽略的点十六进制里 A 到 F 代表 10 到 15不是字母而是单个数字符号。比如0xD9不是一个D 加 9而是D这个符号代表 13整体是一个两位数值是 13×16 9 217。2. 整数转换除基取余法为什么必须从下往上读2.1 十进制转二进制以 217 为例的完整推导十进制整数转二进制标准方法是除 2 取余逆序排列。我见过太多人背结论背错把顺序搞反。这里把原理讲清楚你就不容易错了。把十进制数 N 除以 2得到商和余数。余数就是当前二进制最低位的值。为什么因为任何整数除以 2 的余数只可能是 0 或 1而这正好是二进制 个位 能表达的信息。接下来把商继续除以 2得到的余数就是更高一位的值。一直到商为 0 为止。用 217 完整的走一遍217 ÷ 2 108 余 1 → 第 0 位 108 ÷ 2 54 余 0 → 第 1 位 54 ÷ 2 27 余 0 → 第 2 位 27 ÷ 2 13 余 1 → 第 3 位 13 ÷ 2 6 余 1 → 第 4 位 6 ÷ 2 3 余 0 → 第 5 位 3 ÷ 2 1 余 1 → 第 6 位 1 ÷ 2 0 余 1 → 第 7 位把余数从下往上念出来11011001。验证一下128 64 0 16 8 0 0 1 217完全正确。为什么要从下往上你想想第一次除以 2 得到的余数是 217 里能被 2⁰ 分割出去的部分它属于最低位最后一次除到商为 0 得到的余数才是最高位。顺序念就是低位在前、高位在后和我们平时写数字高位在前的习惯正好相反所以必须倒过来。我当年学的时候也老忘后来就用一句话提醒自己第一次除出来的是末位头版头条永远放最后。2.2 换除数是 8 或 16同一个套路走天下理解了原理转八进制、十六进制就是换个除数的事。217 转八进制217 ÷ 8 27 余 1 27 ÷ 8 3 余 3 3 ÷ 8 0 余 3从下往上读331所以 217 0o331。验证3×64 3×8 1 192 24 1 217正确。217 转十六进制217 ÷ 16 13 余 9 13 ÷ 16 0 余 13即 D从下往上读D9所以 217 0xD9。验证13×16 9 217。这里 13 对应的符号是 D一定要在最后写的时候把 10~15 转成 A~F别直接写成数字 13。2.3 反过来按权展开法把任意进制还原成十进制从任意进制转十进制不需要做除法只需要按权展开。这个方法也叫扩展法就是把我前面说的位置计数法原样落地。八进制 331 转十进制3×8² 3×8¹ 1×8⁰ 192 24 1 217十六进制 D9 转十进制D×16¹ 9×16⁰ 13×16 9 217二进制 10110 转十进制1×2⁴ 0×2³ 1×2² 1×2¹ 0×2⁰ 16 0 4 2 0 22这里要特别提醒从右往左数第一位权重是 16⁰、2⁰、8⁰不是 16¹、2¹。很多人在这一步多算一个倍数得出结果差一截。我习惯在展开前先写清楚每位权重再填数字能有效避免低级错误。3. 小数转换最翻车乘基取整、无限循环与精度舍入3.1 乘基取整法为什么是从上往下读整数部分用除法小数部分用乘法两者方向完全相反这是新手最容易崩的地方。十进制小数转二进制的方法是乘 2 取整顺序排列把小数部分不断乘以 2每次取乘积的整数部分0 或 1作为二进制的一位直到小数部分为 0 或达到要求的精度。举个例子0.625 转二进制0.625 × 2 1.25 → 整数部分 1剩 0.25 0.25 × 2 0.5 → 整数部分 0剩 0.5 0.5 × 2 1.0 → 整数部分 1剩 0.0从上往下读101所以 0.625 0.101₂。为什么方向是从上往下因为第一次乘 2 取出的整数部分是小数点后最高位2⁻¹ 位。小数部分第一位是半个0.625 × 2 1.25意味着 0.625 里包含 1 个 0.5所以二进制小数点后第一位是 1权重是 2⁻¹。这跟整数部分第一次除法得最低位正好是相反的直觉。3.2 0.1 的二进制是个无限循环小数很多人第一次发现这个事实时非常震惊十进制里干干净净的 0.1在二进制里根本写不完只能用无限循环小数近似。来算一下 0.1 转二进制0.1 × 2 0.2 → 0 0.2 × 2 0.4 → 0 0.4 × 2 0.8 → 0 0.8 × 2 1.6 → 1剩 0.6 0.6 × 2 1.2 → 1剩 0.2 0.2 × 2 0.4 → 0 0.4 × 2 0.8 → 0 0.8 × 2 1.6 → 1剩 0.6到这里循环了0.001100110011...₂无限不循环不是不循环是循环循环节是 0011。这就是为什么浮点数在计算机里不能精确表示 0.1只能存一个最接近的近似值。不只是 0.1凡是分母不包含 2 的幂因子的十进制小数转二进制统统是无限循环小数比如 0.2、0.3、0.7 全部中招。这个知识点直接解释了为什么在 Python、JavaScript、Java 里经常看到这种怪象print(0.1 0.2) # 输出 0.30000000000000004不是语言有 bug而是 0.1 和 0.2 在二进制里都是近似值加完之后依然是个近似值。热搜里有人问十进制小数转换为二进制有精度限制时需要考虑舍入吗答案非常明确必须考虑IEEE 754 浮点标准本身就是按最接近的二进制小数舍入的。所以工程上判断浮点数相等几乎从不直接写a b而是判断差值的绝对值是否小于一个很小的数if (fabs(a - b) 1e-9) { // 视为相等 }3.3 二进制小数转回十进制负指数展开二进制小数转十进制比转整数多了个门槛小数点右边的权重是 2 的负指数从 2⁻¹ 开始。二进制 0.101 转十进制1×2⁻¹ 0×2⁻² 1×2⁻³ 0.5 0 0.125 0.625再算一个复杂点的0.1101101₂1×2⁻¹ 1×2⁻² 0×2⁻³ 1×2⁻⁴ 1×2⁻⁵ 0×2⁻⁶ 1×2⁻⁷ 0.5 0.25 0.0625 0.03125 0.0078125 0.8515625记住这张权重表能加快心算速度这是我在调试浮点相关数据时最常用的东西小数位序号权重十进制值第 1 位2⁻¹0.5第 2 位2⁻²0.25第 3 位2⁻³0.125第 4 位2⁻⁴0.0625第 5 位2⁻⁵0.03125第 6 位2⁻⁶0.015625第 7 位2⁻⁷0.0078125第 8 位2⁻⁸0.00390625特别提醒很多新手把二进制小数第一位当成 2⁻⁰这是错的。整数部分第一位权重才是 2⁰1小数点右边第一位权重是 2⁻¹0.5中间隔了一个小数点权重体系就已经换了边界。3.4 带整数的混合数怎么转拆开处理再拼接一个数同时有整数和小数部分时最稳妥的做法是分开转最后拼起来。比如要把 217.625 转二进制整数部分 217 → 11011001₂小数部分 0.625 → 101₂拼在一起就是 11011001.101₂。拼接时注意小数点的位置不能错。有人会问整数部分是 8 位小数部分是 3 位拼起来到底是多少这里没有补齐位数再拼的必要只要小数点位置正确转回十进制验证即可11011001.101₂ 217 0.625 217.625完全正确。如果你偏要补位请记住整数部分在高位补 0小数部分在低位补 0补 0 不能改变数值。后面分组转换时这个方向也会反复用到。4. 二、八、十六进制之间的秒转技巧8421 法则与分组法4.1 为什么可以按 3 位、4 位一组直接映射二进制转八进制、十六进制有一个极其省事的捷径原理就是 2³8、2⁴16。一个八进制数字能表示 0 到 7恰好需要 3 位二进制一个十六进制数字能表示 0 到 15恰好需要 4 位二进制。所以二进制转八进制时把二进制位从右往左整数部分每 3 位切成一组转十六进制时每 4 位切成一组。方向一定记住整数部分从最右边开始分组不足的往左边补 0。很多人在这一步补 0 补反了位置整组结果全乱。4.2 实例二进制 11011001 转十六进制、八进制先把 11011001 转十六进制。整数部分从右往左每 4 位一组1101 1001 D 9所以 11011001₂ 0xD9。这里的映射表是二进制十六进制000000001100102001130100401015011060111710008100191010A1011B1100C1101D1110E1111F这张表不需要背因为它是可计算的。知道 8、4、2、1 四个权重就行1101 841 13 D1001 81 9。这就是8421 法则的由来也是网上那个二进制扩展法的实际应用。再转八进制。从右往左每 3 位一组11011001 切分后是 11 011 001最高位那组只有 2 位需要在左侧补一个 0 变成 011011 011 001 3 3 1所以 11011001₂ 331₈和我们前面用除法算出来的八进制完全一致。4.3 反向操作十六进制 D9 还原成二进制反向一样用 8421 法则。十六进制每一位展开成 4 位二进制D 13 11019 1001拼起来就是 11011001。八进制每一位展开成 3 位二进制331 → 011 011 001 → 11011001。展开时注意每个数字都必须是 3 位或 4 位不够就补 0。比如八进制 331 里的 3 要写成 011 而不是 11否则展开后位与位之间会黏连结果就错了。4.4 小数部分怎么分组小数点是分界线的标杆带小数的二转八、二转十六分组时以小数点为分界线整数部分从紧挨小数点的那个位开始向左分组小数部分从紧挨小数点的那个位开始向右分组。哪边不够一组就在哪边补 0注意方向千万别搞反。以 0.1101101₂ 转八进制为例。小数点向右每 3 位一组110 110 1 6 6 补 00 → 4小数部分最高组是 110中间是 110最末尾只有一个 1要在右边补两个 0 变成 100对应十进制 4所以结果是 0.664₈。验证6/8 6/64 4/512 0.75 0.09375 0.0078125 0.8515625和原数二进制 0.1101101 算出来的十进制完全一样。再转十六进制同样向右每 4 位分组1101 101 D 补 0 → A结果是 0xDA验证13/16 10/256 0.8125 0.0390625 0.8515625依然一致。到这里应该能体会到只要掌握了 8421 法则二与八、十六之间的互转就是视力活完全不需要动笔算乘法。5. 负数、补码与编程落地从论文公式到能跑的代码5.1 负数的二进制补码不是符号位取反这么简单热搜里有负数的二进制十六进制补1这俩其实是同一个东西计算机里负数是用补码表示的。为什么不用最直观的最高位当符号位因为符号加绝对值方案里0 有两种编码正 0 和负 0而且做减法很麻烦。补码把减法统一成加法0 也只有一种表示。求一个负数的补码标准步骤是先写出正数的二进制按位取反再加 1。这里取反加 1就是热搜里说的十六进制补1。举个例子求 -217 在 16 位下的二进制表示217 的 16 位二进制0000000011011001按位取反1111111100100110加 11111111100100111所以 -217 1111111100100111₂ 0xFF27。验证一下0xFF27 按无符号算是 65319但如果当成 16 位有符号整数最高位为 1说明是负数对它再取反加 10xFF27 取反得 0x00D8加 1 得 0x00D9 217所以原数是 -217完美闭环。用 C 语言可以直接看到这个效果#include stdio.h int main(void) { int n -217; printf(n %d\n, n); printf(n as unsigned hex %X\n, (unsigned int)n); printf(n as unsigned dec %u\n, (unsigned int)n); return 0; }输出里你会看到它在内存里的原始位模式确实是 FFFFFF27 这样的形态32 位下。5.2 C 语言里的进制输入输出printf 和 scanf 的格式符日常写代码进制转换经常是编译器在帮你做。C 语言的 printf 和 scanf 提供了现成的格式符格式符作用示例%d十进制有符号整数printf(%d, 217)%u十进制无符号整数printf(%u, u)%o八进制无符号整数printf(%o, 217) → 331%x十六进制无符号整数小写printf(%x, 217) → d9%X十六进制无符号整数大写printf(%X, 217) → D9scanf 相同输入八进制、十六进制数时用户要带前缀吗不一定但格式符会按对应进制解析。比如int a; scanf(%x, a); // 用户输入 d9a 就是 217这是我最常用的调试手段。看内存变量时%x比%d直观得多尤其在排查地址、协议字段、文件头时。5.3 手写一个通用进制转换函数有时候语言没有现成 API或者你需要把转换逻辑嵌进自己的工具链里。这里给一个 C 语言通用版本把十进制整数转成任意 2 到 16 进制字符串递归实现逻辑和除基取余法完全一致#include stdio.h void dec_to_base(unsigned int n, unsigned int base) { const char *digits 0123456789ABCDEF; if (n base) { dec_to_base(n / base, base); } putchar(digits[n % base]); } int main(void) { unsigned int n 217; printf(217 in bin: ); dec_to_base(n, 2); putchar(\n); printf(217 in oct: ); dec_to_base(n, 8); putchar(\n); printf(217 in hex: ); dec_to_base(n, 16); putchar(\n); return 0; }运行结果217 in bin: 11011001 217 in oct: 331 217 in hex: D9这个函数有几个细节值得说第一参数必须用unsigned int因为负数除法取余在 C 老标准里依赖实现用无符号类型可以避免很多坑第二digits[n % base]巧妙处理了十进制数大于等于 10 时对应的 A-F 符号第三递归调用先处理高位再输出当前位省去了手动倒序存储原理就是除基取余法从下往上读。如果你要转小数部分就得用乘基取整的循环void frac_to_base(double frac, unsigned int base, int precision) { const char *digits 0123456789ABCDEF; putchar(.); for (int i 0; i precision; i) { frac * base; unsigned int digit (unsigned int)frac; putchar(digits[digit]); frac - digit; if (frac 1e-12) break; } }注意这里的危险点浮点数无法精确表示所有小数所以循环里要设置一个精度上限否则遇到 0.1 这种无限循环小数循环会一直跑下去。precision参数就是干这个的。5.4 实战场景里我怎么用进制判断问题在我的日常排查里进制转换不是纸上谈兵而是直接看数据。常见的文件开头十六进制特征值非常值得记一份文件类型十六进制开头可读文本PNG 图片89 50 4E 47.PNGJPEG 图片FF D8 FFÿØÿPDF 文档25 50 44 46%PDFZIP 压缩包50 4BPKELF 可执行文件7F 45 4C 46.ELF有一次我拿到一个文件后缀是 .png 但打不开用 HxD 打开一看开头是50 4B立刻判断它其实是个 ZIP。这种判断完全依赖对十六进制特征值的敏感度。还有一个常见问题十六进制文件全反了怎么快速调整——如果你在 HxD 里看到文件头和预期顺序完全倒置比如 PNG 开头变成47 4E 50 89多半是字节序大小端问题。比如你本意是十六进制89 50 4E 47编辑器却按小端模式逐字读成47 4E 50 89。遇到这种情况检查打开方式是否带字节序选项或者在数据解析时手动做字节反转别急着改文件本身。6. 我总结的易错点自查表与快速心算技巧6.1 高频错误的完整排查链路这么多年下来我看过很多初学者卡在同样几个坎。把它们的完整纠错逻辑整理成表比单独背公式有用得多错误现象根本原因排查思路与修正转换结果差一个数量级权重从 2⁰ 那一位开始数错用了 2¹ 起步先写出每位权重再套数字重点检查最右位。比如 11011001 最右位是 2⁰1不是 2¹整数除法余数顺序写反没理解第一次除得的是最低位用 217 反向验证如果写出来是 10011011按权展开一定不是 217。记住头一回余数在最后小数乘 2 结果顺序写反把整数部分的从下往上惯性带过来了小数第一位是 2⁻¹ 位是最高位必须从左往右读。0.625 转出来是 101 不是 101 也反向读二转八时在高位补 0 方向错分组补位方向不清晰整数部分左侧补 0小数部分右侧补 0。11011001 转八进制如果从右往左分组高位那组 11 补成 011十六进制 A~F 忘了转换十进制 10~15 不能直接写在十六进制里13 → D12 → C反向时 D → 13。建议默念A10 B11 C12 D13 E14 F15负数的二进制直接写符号没有用补码负数先求正数二进制取反加 1。别在最高位硬贴一个 1 就完事浮点数比较用 不知道十进制小数转二进制有精度损失用 fabs(a-b) 1e-9 之类的方式比较或转成整数做定点计算我在自测的时候会用一个反推法拿到转换结果立即按权展开回十进制看能不能得到原数。这个验证成本极低却能拦截 90% 的低级错误强烈建议养成习惯。6.2 心算检查两位数秒估二进制位数除了完整验证还有几个快速心算技巧可以边算边查二进制位数粗估记住 2¹⁰ 1024 ≈ 1000所以一个约 1000 的数要用 10 个二进制位约 200 的数在 2⁷128 和 2⁸256 之间所以 217 需要 8 位二进制。算出结果如果位数和估计差太远肯定有地方错了。奇偶检查十进制数是奇数二进制末位必须是 1是偶数末位必须是 0。217 是奇数所以末位一定是 1正好对应 11011001 的最后一位。十六进制快速看二进制看到 0xD9心算 D841也就是 1101981也就是 1001拼起来就是二进制 11011001。看到0xFF就知道 8 个 1对应十进制的 255。8421 权重表熟记8、4、2、1 这四个值足够覆盖 0~15 的所有组合。比如 1010 82 10 A0110 42 6。这些技巧在调试协议、看内存 dump、分析 CTF 二进制题目时都特别好用。我遇到十六进制地址的第一反应不是掏计算器而是先心算个大概数量级再确认细节。6.3 工具辅助该用计算器时别硬算虽然这篇文章在讲手算但实际干活时该用工具就用工具。Windows 计算器切到程序员模式、Python 的bin()oct()hex()int()都比手算快得多print(bin(217)) # 0b11011001 print(oct(217)) # 0o331 print(hex(217)) # 0xd9 print(int(d9, 16)) # 217VS Code 用户如果需要直接看二进制文件内容可以装 HexEditor 插件打开文件就是十六进制视图我平时排查二进制数据主要用 HxD轻量、打开大文件也流畅。工具会用了再回头看手算方法反而更容易理解这些工具背后在做什么——它们不过是把我在 2.1 和 3.1 里写的那两套循环用机器速度跑了一遍而已。我自己每次教人进制转换都会把除基取余、乘基取整、8421 分组、补码取反加一这四件事当成一个整体来讲。这四件事不是孤立技巧而是同一条位置计数法在不同场景下的影子。把那句每一位都是符号乘以进制权重的累加刻进脑子其他所有方法都是它的派生结论。遇到现场的畸形数据、奇怪输出先回到原理层推一遍往往比搜半天社区帖子更管用。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →