拓冰建站拓冰建站
首页 / 资讯中心 / 正文

进制转换全攻略:从二进制到十六进制、补码与精度问题一网打尽

经常有人问我“你做调试的时候看到那堆十六进制怎么就反应得过来”其实真不是天赋这是整天和二进制、十六进制打交道磨出来的本能。进制转换这个事在教科书里往往就是几页纸但真正用起来——比如分析协议、看内存、解析文件头、写位运算——你会发现它不只是入门课而是伴随整个职业生涯的基础工具。这里我把自己梳理过的整数、小数、负数、快速换算还有各种踩坑经验一起整理出来花半个小时读一遍以后遇到进制问题就不用再翻书了。1. 为什么要学进制转换从“数数方式”到“机器语言”1.1 四种进制的使命各不同先说清楚一个本质问题二进制、八进制、十进制、十六进制本质上只是同一个数值的四种不同“写法”。就像“10个苹果”你可以说“十个”可以说“一打少两个”数字本身没变变的是计数规则。十进制是我们日常生活中最自然的计数方式因为人有十根手指十个一组进位人类用了一辈子数学直觉都建立在十进制上。但在计算机世界里电路只有两种稳定状态有电没电、高电平低电平于是二进制成为唯一能让电子元件直接表达的语言。1表示有电0表示没电简单可靠这就是一切运算的物理基础。那八进制和十六进制又是干嘛的二进制虽然对机器友好对人太不友好了。一串1101011011000010谁看着都头疼。但是发现没有3位二进制正好能表示0到7对应1位八进制4位二进制正好能表示0到15对应1位十六进制。也就是说八进制和十六进制是二进制的“缩写”读起来省眼睛转回去又非常快所以程序员、网络工程师、嵌入式开发者才会把它们当成二进制的“人类友好界面”。1.2 进制转换到底在解决什么问题你实际遇到的进制转换场景远比考试题刺激。IP地址那四个数字段本质上就是8位二进制文件头里FF D8 FF E0是JPEG图片的标识内存地址在调试器里用十六进制显示RGB颜色值是十六进制Unicode字符集和编码表也大量用十六进制标注。这些场合不会有人给你一张十进制表你得在脑子里完成进制之间的自由切换。掌握进制转换解决的是三个层面的问题读得懂看到一列十六进制字节能快速判断文件类型、协议字段含义。算得准需要自己拼报文、计算偏移量、构造数据时不会因为进制搞错而出bug。聊得来跟设备、工具、代码打交道时能理解底层为什么这么设计。所以这不是“学了有什么用”的问题而是现代技术栈里绕不开的基本功。接下来我按整数、小数、负数、快捷换算几个方向把底层逻辑完整讲一遍。2. 整数部分转换按权展开与短除法一网打尽2.1 任意进制转十进制按权展开法先从最简单的说起任何进制转十进制都用“按权展开”。什么叫“权”就是一个数字所在的位置对应的单位。拿十进制数 217 来说它其实是 2×10² 1×10¹ 7×10⁰。这里的 10²、10¹、10⁰ 就是“权”数字 2、1、7 就是这一位上的“系数”。同样的道理二进制数 11011001 转十进制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₁₀你会发现按权展开就是“每个位上的数字乘以它的权重再全部加起来”。无论二进制、八进制还是十六进制这个方法都一样只是把底数从10换成2、8、16而已。所以十六进制D9转十进制就是 13×16¹ 9×16⁰ 208 9 217。我记得刚学的时候总爱死记硬背比如“二进制逢二进一”其实理解了“权”这个概念整条线就通了进制转换就是换一套“进位规则”而按权展开是把任何进制的数变回十进制的万能钥匙。2.2 十进制转二进制短除法为什么是“除2取余”反过来十进制转二进制教科书教的是“除2取余倒序排列”。很多人照着做但不知道为什么要这么做。我换个角度解释给各位一个十进制数要拆成二进制其实就是不断判断“它最多包含多少个2的幂”而且要从最低位开始拆。例把 217 转成二进制。217 ÷ 2 108 余 1 108 ÷ 2 54 余 0 54 ÷ 2 27 余 0 27 ÷ 2 13 余 1 13 ÷ 2 6 余 1 6 ÷ 2 3 余 0 3 ÷ 2 1 余 1 1 ÷ 2 0 余 1然后从下往上倒着读余数11011001₂。为什么倒着读因为每次除以2得到的余数恰恰是当前最低位的二进制位。第一次除2的余数是 217 对2取模决定了个位是0还是1第二次的余数决定了2¹位第二次是2²位……以此类推。最后一次除到商为0剩下的1就是最高位。倒着排列就是按从低位到高位的自然顺序。搞清楚原理之后你甚至可以口算一些小数字。比如判断一个数是不是偶数看二进制最后一位就知道所以连续除2取余的过程中最后一位直接确定了。这个方法对任何进制都通用十进制转八进制就“除8取余”转十六进制就“除16取余”唯一多一步的是十六进制里余数可能大于9要用A到F表示。2.3 十进制转八进制和十六进制换底就行明白了上面这套转八进制、十六进制就没有任何心理负担了。217 ÷ 8 27 余 127 ÷ 8 3 余 33 ÷ 8 0 余 3倒序排列331₈。验证一下3×8² 3×8¹ 1×8⁰ 192 24 1 217完全一致。217 ÷ 16 13 余 913 ÷ 16 0 余 1313对应十六进制的 D倒序排列D9₁₆。验证13×16 9 217。说到这插一句网上的“进制转换c语言”“进制转换计算器”到处都是但我一直建议新手至少手算几十道题。手算最大的价值不是练计算而是建立“数感”看到一个大数能大概猜出它二进制有几位2¹⁰10242¹⁶65536这对后面理解内存空间、数据范围都有巨大帮助。3. 小数部分转换乘2取整与精度的残酷真相3.1 十进制小数转二进制乘2取整法整数部分用短除法小数部分就完全反过来了用“乘2取整正序排列”。规则是这样的小数部分乘以2取整数部分作为二进制位然后把小数部分剩下的值继续乘2直到小数部分变成0或者达到你需要的精度。以 0.8125 为例0.8125 × 2 1.625取整数部分 1剩下 0.6250.625 × 2 1.25取 1剩下 0.250.25 × 2 0.5取 0剩下 0.50.5 × 2 1.0取 1剩下 0所以 0.8125₁₀ 0.1101₂。验证一下1×2⁻¹ 1×2⁻² 0×2⁻³ 1×2⁻⁴也就是 0.5 0.25 0.0625 0.8125完全正确。为什么要乘2取整因为十进制小数转二进制时二进制小数第一位代表2⁻¹也就是0.5。原始小数乘以2之后如果结果大于等于1说明它至少包含一个0.5那么这个二进制位就取1否则取0。这跟整数部分“除2取余”是镜像对称的操作理解这个对称性就永远不会搞混方向。笔试、面试里经常考的 0.1₁₀ 转二进制你会发现乘2取整进入死循环0.1 × 2 0.2 取00.2 × 2 0.4 取00.4 × 2 0.8 取00.8 × 2 1.6 取1剩 0.60.6 × 2 1.2 取1剩 0.2然后就开始循环了0.1₁₀ 0.0001100110011001100...₂ 无限循环。这就是为什么计算机用浮点数存 0.1 时会出现精度误差不是计算机笨是二进制世界里 0.1 根本写不完。如果你在金融系统里做金额计算用浮点数直接算就是一种灾难很多项目会用整数分单位或者专门的十进制库来规避根源就在这。3.2 精度限制下需要考虑舍入吗热词里有一条问“十进制小数转换为二进制有精度限制时需要考虑舍入吗”我的答案是必须考虑而且舍入方式直接影响结果。二进制舍入的思路和十进制四舍五入很像。保留到小数点后第n位时看被舍掉部分的最高位是0还是1如果下一位是0直接舍去这相当于四舍五入里的“四舍”。如果下一位是1就在保留的最后一位上加1这相当于“五入”。举个例子0.7₁₀ 转二进制0.7 × 2 1.4 取1剩0.40.4 × 2 0.8 取0剩0.80.8 × 2 1.6 取1剩0.60.6 × 2 1.2 取1剩0.20.2 × 2 0.4 取0剩0.4无限循环0.101100110011...₂。如果保留4位二进制小数写出来是 0.1011下一位需要舍去的是0直接舍。0.1011₂ 0.5 0.125 0.0625 0.6875误差0.0125还能接受。如果保留3位先写出前4位 0.1011只需保留3位是 0.101被舍掉的最高位是1所以要进位结果是 0.110₂ 0.75误差0.05。再极端一点保留1位前2位是0.10保留1位就是0.1被舍掉的最高位是0舍去结果是0.5误差就大了。所以实际工程里确定要保留多少位之后多算一位再判断是否进位是常态。不要天真地以为“截断就是直接砍掉”很多项目里一个看似无关紧要的进位累积下来就是大问题。3.3 二进制小数转十进制负幂次展开从二进制小数回到十进制方法和 2.1 节的按权展开一样只是权重变成了负次幂。比如二进制 0.1101₂1×2⁻¹ 0.51×2⁻² 0.250×2⁻³ 01×2⁻⁴ 0.0625加起来就是 0.8125。实用技巧二进制小数里第1位永远是0.5第2位是0.25第3位是0.125第4位是0.0625。你只需要记住这几个常用权值口算大部分二进制小数都没问题。如果位数多了比如 0.10110011₂那就按权展开一个个加虽然繁琐但不会错。4. 二进制、八进制、十六进制之间的捷径换算4.1 为什么可以在二进制、八进制、十六进制间直接分组整数和小数部分都搞定之后还有一个非常实用的技能二进制、八进制、十六进制之间互转根本不需要先转十进制再转回来用分组法可以直接秒杀。核心原理就是前面的“缩写”关系3位二进制能表达的0到7正好对应1位八进制4位二进制能表达的0到15正好对应1位十六进制。所以二进制转八进制只需要把二进制从右往左每3位一组最后一组不足3位就在左边补0二进制转十六进制就是从右往左每4位一组最后一组不足4位在左边补0。然后把每组直接当成独立的小数转成对应进制就行。反过来八进制转二进制就是把每个八进制位展开成3位二进制十六进制转二进制就是把每个十六进制位展开成4位二进制。4.2 整数部分分组换算实操举个例子二进制101101转八进制从右往左每3位一组101 101101₂ 5₈101₂ 5₈结果就是 55₈验证101101₂ 32 8 4 1 45₁₀55₈ 5×8 5 45一致。再转十六进制从右往左每4位一组10 1101不足4位左边补0变成 0010 11010010₂ 2₁₆1101₂ D₁₆结果就是 2D₁₆验证2×16 13 45完全一致。反向操作也一样。十六进制2D转二进制2 → 0010D → 1101拼起来是 00101101去掉前导零就是101101。八进制55转二进制5 → 1015 → 101结果是101101。这里我建议你把0到15的二进制写法背熟因为它们在后续所有分组操作里都是直接映射关系。用多了之后看到十六进制一个字符你的大脑会直接弹出对应的4位二进制不用再经过十进制中转。4.3 小数部分的分组换算补位方向千万别搞反分组法里最大的坑也是我见过无数新人出问题的地方是小数部分的补位方向。整数部分分组是从右往左因为最高位那边可能缺位要往左边补0。小数部分正好相反小数分组要从小数点后第一位开始从左往右每3位或4位一组最后一组不足位数时要往右边补0。为什么往右补因为小数部分末尾补0不改变数值这是数学上允许的而小数部分不能往左补0否则会把数值人为扩大。举个具体例子二进制0.101101转八进制小数点后从左往右每3位一组101 101101₂ 5₈101₂ 5₈结果0.55₈如果二进制小数位数不是3的倍数比如0.1011从左往右分组101 1最后一组只有1位右边补两个0变成 100所以 101₂ 5₈100₂ 4₈结果0.54₈验证一下0.1011₂ 0.5 0.125 0.0625 0.6875₁₀。0.54₈ 5×8⁻¹ 4×8⁻² 0.625 0.0625 0.6875完全正确。如果你往左补0那组就变成 011 也就是3₈结果就错了。转十六进制同理0.1011转十六进制从左往右每4位一组正好就是1011₂ B₁₆结果是0.B₁₆。如果小数位是0.10110就需要往右补0成1011 0000得到B0也就是0.B0₁₆当然十六进制小数末尾的0可以省略。分组法唯一的注意事项就是整数左边补0小数右边补0成了这个口诀你的进制换算速度会提高一个量级。5. 负数的二进制表示补码从原理到实战5.1 为什么要搞补码前面讲的都是正数但现实里负数太常见了。负数在计算机里怎么表示有几种方案最直观的是“符号位表示法”比如用最高位0表示正、1表示负剩下位存绝对值。但符号位法有两个致命问题一是正负0并存00000000表示 010000000表示 -0两个零会带来大量判断麻烦二是加减法不能直接统一计算5-3和5(-3)得走两套逻辑硬件上很别扭。于是计算机采用了补码方案。补码的核心设计目标就是让减法变成加法让0唯一表示让硬件电路简单可靠。现代所有主流CPU存储有符号整数用的都是补码。5.2 补码怎么算取反加一补码转换规则很好记正数的补码就是它本身的二进制负数的补码先把它的绝对值写成二进制然后按位取反再加1。拿 -5 来说假设用8位二进制5 的二进制是 00000101按位取反11111010加111111011所以 -5 的8位补码是11111011。为什么必须取反加一而不是直接减一因为你可以验证一下补码 原码的绝对值理论上应该等于0。0000010111111011100000000超过8位溢出的最高位丢掉了剩下00000000完美归零。这就是补码巧妙的地方。从补码反推负数也有两种方法。方法一先减1再取反得到绝对值。11111011减1得11111010取反得00000101也就是5所以它是-5。方法二直接用带符号位的按权展开把最高位的权重当作负的2^(n-1)。8位补码里最高位权重是-128所以11111011₂ -128×1 64×1 32×1 16×1 8×1 0×2 1×1 1×1 -128 64 32 16 8 2 1 -5这个方法熟悉之后其实更快而且不容易出错尤其在笔试手算二进制补码转十进制时强烈推荐。5.3 补码实战中的常见误区网上热词里有关“负数补1”的讨论通常来自两个场景一个是算术右移时对于负数最高位补1这是移位运算规则不是补码转换另一个是对补码取反时有人以为“高位也要跟着补1”这是错误联想。补码转换时的取反是对固定位数内的每一位取反正数左侧多余的0也要取反成1。比如5的8位表示00000101取反是11111010此时左侧高位全部变成了1这是正常的它表示这是个负数。但如果你写成5的二进制101取反得到010再加1得011这就是3了完全错误。所以计算补码时第一件事明确位数。8位、16位、32位的补码结果完全不同。-5在8位下是11111011在16位下是1111111111111011。位数不写清楚补码就是薛定谔的猫。6. 常用工具与自查速查表、文件头与程序实现6.1 进制速查对照表和手算校验技巧进制转换虽然上手容易但真要快速反应少不了一张常用对照表。下面这个0到15的四进制对照表建议你熟练到条件反射的程度因为十六进制和二进制分组全靠它十进制二进制八进制十六进制00000001000111200102230011334010044501015560110667011177810001089100111910101012A11101113B12110014C13110115D14111016E15111117F你可能注意到了八进制那一列到了8就变成1016变成20因为它逢八进一。这对刚接触八进制的人很有迷惑性8在八进制里写 10而不是 8。我自己初学时就犯过这个错以为八进制里能看到8这个数字实际上八进制根本不存在数字8只有0到7。另外建议记住2的幂2⁰12¹22²42³82⁴162⁵322⁶642⁷1282⁸2562⁹5122¹⁰10242¹¹20482¹²40962¹³81922¹⁴163842¹⁵327682¹⁶65536。手算校验时有个偷懒技巧拿217举例它比128大比256小最高位一定是2⁷位217-1288989比64大所以2⁶位也是189-642525比32小2⁵位是025比16大2⁴位是1……这么一路减下去比列除法竖式快得多而且自带验证逻辑。熟悉之后大数转十六进制你也能口算个大概。6.2 读文件头、用hex编辑器分析的小技巧进制转换在实际工作中最常见的应用之一就是看文件头。用十六进制编辑器打开一个文件前几个字节几乎就是文件身份的“身份证”这些固定开头在行业里叫文件签名、魔数或者Magic Number。常见的有PDF25 50 44 46等价于ASCII字符串%PDFPNG89 50 4E 47等价于\x89PNGJPEGFF D8 FF E0或FF D8 FF E1ZIP50 4B 03 04等价于PK..GIF47 49 46 38等价于GIF8ELFLinux可执行文件7F 45 4C 46等价于\x7FELF有了这个知识很多问题迎刃而解一个文件改了后缀打不开你用十六进制编辑器看一眼开头马上知道它真实格式是什么。这也是网安、逆向、取证方向入门必会的基本功。如果你用HxD这类十六进制编辑器看文件头、定位特定字节序列都很方便。实际排查问题的时候我经常用它的“搜索十六进制值”功能直接搜FF D8找JPEG数据段起点比在代码里肉眼找快太多。读取一段连续的二进制数据时识别某个字段的大端小端顺序也很重要比如一个16位数值0x1234在内存里存成34 12说明它是小端字节序。大多数X86机器和Windows文件格式都是小端网络协议则更常用大端。这个判断能力本质上还是进制换算和字节序理解在起作用。6.3 程序实现时的典型易错点热词里有一条“c语言strstr()能否用于查找二进制内存”这里统一回答不能。strstr是字符串查找函数它以\0作为结束标志而二进制数据里随时可能出现0x00一旦遇到\0就会提前停止根本搜不到后面的内容。想在一段二进制内存里搜索特征值要用memmem或者自己写按字节比较的循环这是处理二进制协议、文件解析时特别常见的坑。另一个高频坑是进制转换涉及的字符串和数字混淆。比如十六进制字符串FF和数字0xFF完全是两回事前者是字符数组后者是整数255。从外部读进来的一串十六进制文本你得先自己解析成数值才能参与运算。C语言里可以strtol(str, NULL, 16)Python里是int(str, 16)但千万别直接用atoi那只会把FF转成0。再说回小数问题上一章讲的0.1精度问题在浮点数里天天发生。如果你在一个调度任务里频繁判断“当前时间是否等于某个小数”用浮点数直接比较基本等于赌博。通常做法是把这类小数扩大成整数再比较或者设一个容忍误差范围。理解了“0.1在二进制里无限循环”之后再回头看那些诡异bug你会觉得程序设计里的种种约束其实早就在二进制层面埋下了伏笔。我在实际调试中还有个习惯不管是写协议解析还是排查文件异常手边常年备着一张十六进制ASCII对照表遇到一串48 65 6C 6C 6F能立刻反应出这是Hello。当你把进制转换变成直觉你就不再是“照着文档调接口”的人而是能真正看到数据底层在发生什么的那种开发者。这套基本功值得每个技术人花点时间磨扎实。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门