平时写代码的时候估计不少人和我一样看到一串二进制标志位就头皮发麻尤其是和底层设备、协议报文打交道的时候十六进制倒是能混个眼熟但一旦要求把十进制与二进制互转脑子里就开始翻算法书。前阵子同事调试一个串口设备解析状态寄存器时把位序搞反了当场排查了三个小时才发现是十进制转二进制时一个小环节出了差错。这种看起来“人人都会”的基础操作真到用时反而最容易栽跟头。这篇文章我会完整讲透十进制与二进制互转这件事从原理层面讲清楚为什么要这么转再给出一套能直接抄作业的实操方法包括整数、小数、快速心算、编程实现和常见坑位。适合刚入门编程的朋友也适合常和底层数据打交道的开发想查漏补缺的同样可以看。1. 先搞懂进制到底在说什么1.1 位置计数法从数手指到数状态我们从小用的十进制本质上是一种“位置计数法”。所谓位置计数法就是同一个数字符号放在不同的位置上表达的大小完全不一样。拿“25”这个数来说十位上的“2”表示2个10个位上的“5”表示5个1把它写成展开式就是2×10¹ 5×10⁰。这里的关键就是“位权”的概念。十进制的位权是10的幂次个位是10⁰1十位是10¹10百位是10²100以此类推。每一位上的数字乘以对应位权再把结果加起来就是这个数真正的数值。那为什么偏偏是10这大概率是老祖宗数手指的习惯延续来的人有十根手指十进制对人类最自然。但十进制只是一个约定俗成的选择世界上还有很多其他进制的生活遗迹比如钟表用的是60进制和12进制一小时60分钟、一年12个月一个星期7天是7进制这些都能说明“进制”本身不是天然唯一的规则而是为了计数方便。1.2 为什么计算机偏偏用二进制计算机内部不用十进制最直接的原因是物理造不出足够稳定的十进制器件。早期的继电器、后来的晶体管最可靠的状态就是两个通和断、高电平和低电平、有电和没电。用这两种状态来表示数就是电阻最低的方案天然就是二进制。二进制和十进制在数学地位上没有高低之分只是一个计数习惯。二进制只有0和1两个符号逢二进一所以它的位权是2的幂次从右往左依次是2⁰、2¹、2²、2³……也就是1、2、4、8、16、32、64、128……理解到这一步十进制与二进制互转的核心逻辑其实已经浮出水面了本质就是在“以10的幂次展开”和“以2的幂次展开”这两种表达方式之间来回换算。后面所有的具体方法都是围绕这个核心逻辑展开的。2. 十进制转二进制两种方法吃透2.1 整数部分除2取余法的完整过程十进制整数转二进制标准方法是“除2取余逆序排列”。这个口诀几乎所有教科书都会讲但很多人只记了流程没理解为什么要逆序。我拿十进制数27来完整走一遍过程步骤127 ÷ 2 13余数 1步骤213 ÷ 2 6余数 1步骤36 ÷ 2 3余数 0步骤43 ÷ 2 1余数 1步骤51 ÷ 2 0余数 1除到商为0就停止。把余数从下往上倒着读11011。所以十进制的27就是二进制的11011。为什么余数要倒着读因为最后一次除法得到的是二进制最左边的最高位而第一次除法得到的余数是最低位。你要是顺着读成11011加上第一次余数放前面那就会得到完全不同的数。我见过很多新手在这里犯错把27转出10110或者别的奇怪结果就是因为余数顺序没弄明白。这里有一个验证的小技巧把得到的二进制11011按位权展开验证一下——1×2⁴ 1×2³ 0×2² 1×2¹ 1×2⁰ 168021 27刚好对上。以后每次手动转完花十秒钟按这个方法验算一遍能避免九成以上的低级错误。2.2 小数部分乘2取整法与精度陷阱十进制小数转二进制用的是“乘2取整顺序排列”的方法。这个“顺序”和整数的“逆序”刚好相反是很容易混淆的点。我拿0.625来举例步骤10.625 × 2 1.25取出整数部分 1留下0.25步骤20.25 × 2 0.5取出整数部分 0留下0.5步骤30.5 × 2 1.0取出整数部分 1留下0.0当小数部分变成0时终止。把整数部分从上往下顺序读取101。所以十进制0.625就是二进制0.101。为什么顺序读因为小数部分的高位是乘出来的第一个整数这个顺序是天然从高位往低位走的和整数的倒读逻辑正好相反。很多人在同一个项目里既转整数又转小数很容易把“整数倒序、小数顺序”记混干脆用一个更大的原则来记每次运算的结果谁先出来就离小数点最近就把谁放在离小数点最近的位置。整数部分先出来的余数是最低位所以要放最后小数部分先出来的整数是最高位紧挨小数点的第一位所以要放最前面。小数转二进制还有一个现实中的大坑并不是所有十进制小数都能被二进制精确表示。最典型的例子就是0.1。0.1转成二进制是0.00011001100110011……无限循环下去永远除不尽只能在某个精度位置截断。这就是为什么你在JavaScript里输入0.10.2得到的结果是0.30000000000000004的根源二进制根本没法精确表示0.1。所以如果哪天你遇到“为什么我转出来的小数带回十进制后对不上”的问题先别怀疑算法先确认是不是遇到了无法精确表示的循环二进制小数。工程上处理这个问题的通用策略是按需求保留足够的有效位数而不是追求绝对的数学相等。2.3 实操演示把一个带小数点的数完整转一遍把整数部分和小数部分的方法合在一起就能转任何十进制数了。比如要转27.625整数的27转成二进制11011小数的0.625转成二进制0.101。两部分拼起来中间点上小数点11011.101。这里有个容易被忽略的细节拼接时整数部分末尾不要画蛇添足补前导零小数部分最低位也不要随便补零。二进制11011.101和011011.1010在数学上相等但书写习惯上通常保持最简形式除非是在定长存储协议里明确规定了位宽才需要补足到固定长度。我个人的实操习惯是任何一次手动转换都必须分两步走第一步只算第二步验算。验算方式就是把这二进制按权展开确认能回到原数。这样每次转换都能及时发现错误而不是到了后续环节才被坑到。3. 二进制转十进制按权展开与心算技巧3.1 按权展开法的原理和步骤二进制转十进制比反方向要直观得多核心就是“按权展开求和”。把每一位二进制数字乘以它的位权再加起来就行。看二进制数1101从右往左位权分别是2⁰、2¹、2²、2³也就是1、2、4、8。那么1×8 1×4 0×2 1×1 8 4 0 1 13所以二进制1101就是十进制13。这里最容易忽略的是位权从2⁰开始而不是从2¹开始。有些人算二进制1010时习惯从左边第一位开始乘把1010算成1×161×8或者别的什么最后结果完全不对。正确做法是只看它在第几位从右数从0开始就乘以2的几次方。另外说一个和十进制对照的理解方法。十进制里的“123”拆开是1×1002×103×1本质是1×10²2×10¹3×10⁰。二进制完全同理只是底数从10换成了2。明白了这一点按权展开就是从十进制习惯里自然迁移过来的不用死记公式。3.2 心算加速8421码与常用对照表按权展开虽然每一步都很简单但手工算一长串二进制还是会累。这里推荐一个非常实用的心算技巧8421法。二进制的低四位位权正好是8、4、2、1这四位恰好能表示0到15的所有十进制数。看到四位二进制的时候直接把这四个权值依次标在数字下面然后只把对应位置是1的权值加起来就行。举个例子二进制1011标上8421第4位是1就是8第3位是0就是0第2位是1就是2第1位是1就是1加起来802111。整个过程一两秒钟就能完成。多位二进制怎么办从右向左每四位分成一组每组独立用8421法最后组合起来。比如10110110从右向左切分成1011和0110两组1011是110110是6所以整体是182。这个方法同时还是日后理解十六进制转换的基础因为每四位二进制恰好对应一位十六进制数算是把基础一步打结实。我整理了一张最常见的对照表建议背到条件反射的程度十进制二进制十进制二进制008100011910012101010103111110114100121100510113110161101411107111151111这张表格在IP地址计算、权限位图解析、协议头分析时都特别常用。比如子网掩码255.255.255.0本质上就是24位全是1加8位全是0看到二进制就能直接理解十进制为什么是255。3.3 带小数的二进制怎么还原二进制小数转十进制核心规则和整数部分完全一致小数点左边按整数的位权展开小数点右边按负幂次展开。小数部分从紧挨小数点的位置开始位权依次是2⁻¹、2⁻²、2⁻³也就是0.5、0.25、0.125、0.0625……拿二进制0.101举例1×0.5 0×0.25 1×0.125 0.5 0 0.125 0.625这里建议用分数来辅助理解会更稳。2⁻¹是1/22⁻²是1/42⁻³是1/8。每一个位上的1相当于加了1/2或1/4或1/8这样一块这些分数值加起来就能还原出十进制小数。实际开发中你会更常遇到“二进制小数转十进制”的场景吗其实不多。我更常见的是十六进制浮点字面量或者十六进制颜色值的换算但理解负数幂次这部分知识能帮你搞明白浮点数尾数部分的存储逻辑属于典型的“现在学用不上用到的时候后悔没学透”的内容。4. 工程实战编程实现与常见场景4.1 用代码实现互转从现成函数到底层原理日常写代码时十进制与二进制互转分两种需求。第一种就是直接调用语言自带能力图省事第二种是理解底层原理后自己实现适合面试或者做嵌入式开发时不能用标准库的情况。先从现成工具说起。Python里十进制转二进制可以调用bin()函数decimal_number 27 binary_string bin(decimal_number) print(binary_string) # 输出 0b11011注意bin()返回的是字符串带0b前缀要去掉前缀用[2:]切片即可。反过来二进制字符串转十进制用int()函数并指定第二参数为2binary_string 11011 decimal_number int(binary_string, 2) print(decimal_number) # 输出 27JavaScript里没有直接转任意进制字符串的内置函数写起来要绕一点let decimal 27; let binary decimal.toString(2); // 11011 let back parseInt(binary, 2); // 27如果面试或者项目要求不依赖这些封装好的函数自己实现一遍也非常简单。拿“手写十进制整数转二进制”来说本质就是把除2取余法翻译成循环def decimal_to_binary(n): if n 0: return 0 digits [] while n 0: digits.append(str(n % 2)) n n // 2 return .join(reversed(digits))二进制转十进制的手写实现就是把按权展开翻译成循环def binary_to_decimal(s): result 0 for ch in s: result result * 2 int(ch) return result这个二进制转十进制的写法非常漂亮它从最高位开始读每读一位就先把当前结果乘2再加上新读到的位。怎么理解其实就是在模拟按权展开的过程但因为是从左往右迭代无需预先计算位权代码简洁得多。4.2 现实场景权限位、协议解析、配置计算学好进制转换不是为了考试真实项目里它出现的频率比你想象中高得多。最常见的是权限位设计。很多系统用一个整数保存一组开关状态比如某个配置项的值是0b11001从低到高每个bit代表一个独立开关第一个bit是1表示开启短信通知第二个bit是0表示关闭邮件提醒第三个bit是0表示关闭推送第四个bit是1表示开启异常告警第五个bit是1表示开启自动备份。这种位图式的表达可以把多个布尔值压缩到一个数字里极大地节省存储空间也便于批量读写。排查这类位图问题时我最常用的办法就是写一个转换小工具输入十进制数输出每一位的01状态。这一步看起来平平无奇但真能救命曾经查过一个序列号解析问题状态寄存器的第15位才是保留位结果我理解反了排查了很久才发现是二进制编号从0开始还是从1开始的问题。另一个高频场景是网络IP地址。IPv4地址本质上是一个32位二进制数为了人类阅读方便才拆成四组十进制比如常见的192.168.1.1实际上是11000000101010000000000100000001的十进制点分显示。子网划分和路由聚合计算时把人可读的十进制地址转成二进制位串才能看清网络号与主机号的分界位置。还有颜色值换算。网页里的#FF8800本质上是十六进制表示而FF就是二进制的11111111。做前端开发时经常需要在RGB十进制值、十六进制颜色值和二进制位模式之间来回切换比如要提取某个颜色值的红色分量核心操作就是移位和与运算这背后还是进制理解。4.3 位运算视角下的进制转换位运算和进制转换有非常直接的关系理解了这个视角转换就不仅是机械操作而是能理解计算机真正在做什么。先看最基础的规律二进制数左移一位相当于乘以2右移一位相当于除以2。十进制15二进制1111左移一位变成11110就是十进制30。这个规律在进制转换中最直观的应用就是判断一个数除以2的余数等价于看它的最低位是0还是1。一个十进制数如果是偶数二进制最低位必为0如果是奇数最低位必为1。用位运算来实现十进制转二进制也很直观。每次用 1提取最低位再右移一位就能从低位到高位逐位取出二进制表示。这段Python代码本质上就是除2取余法的位运算版本def decimal_to_binary_bitwise(n): bits [] while n 0: bits.append(str(n 1)) n n 1 return .join(reversed(bits))的奇偶判断也是通过位运算完成的n 1等于1就是奇数等于0就是偶数。平时判断奇数别用n % 2位运算更直接也更能体现对进制的理解。走位运算这条线还能串起很多零碎知识比如八进制其实就是每三位二进制合并成一位十六进制是每四位二进制合并成一位。这也是为什么很多底层领域工程师喜欢用十六进制而非十进制交流位模式——一个十六进制位干净对应四位二进制比十进制和二进制的换算关系清晰太多。5. 常见错误、边界情况与考试高频坑5.1 五大高频错误与避坑清单我把这几年翻车经验整理成了一张表几乎覆盖了所有初学者会踩的坑错误类型具体表现解决办法余数顺序读反27转成10110而不是11011记住整数倒序、小数顺序最后用权展开验算位权从1开始数把2¹当第一位权重结果整体翻倍出错记住最右边是2⁰从0开始编号小数部分未置零终止条件无限循环算下去除尽到0就停除不尽就按需求截断正负号处理错误把负数直接按整数方法转负数在计算机里走补码流程别简单套公式补零位置看错拼接整数小数时位序颠倒先算整数和小数再拼拼完反向验算第一类错误最阴险的地方在于转出来的二进制看起来长度差不多只是内部位序乱了不仔细算根本发现不了。所以我反复提醒大家一定养成验算习惯每次转换都用“二进制按权展开回十进制”来确认结果。还有一种错误不是操作层面而是概念层面的用电脑自带的计算器转出来编号0b开头就忘记去掉前缀。写代码时int(0b11011, 2)会报错因为Python的int()不允许裸的0b前缀字符串需要显式指定进制或者去掉前两位。这个是真实项目会遇到的坑。5.2 边界与特殊值0、负数、浮点进制转换里有一批特殊值单独拎出来说。首先是0。十进制的0转二进制还是0这个没争议。但很多人用除2取余法转0时会进入死循环因为0除以2商还是0永远不为0。所以写转换函数时必须先判断输入是不是0直接返回0。这个判断放在函数开头别等循环写完好几个测试用例都过不了才发现。其次是负数。计算机里负数不是拿简单的“负号绝对值二进制”存储的而是用补码。以8位为例-1的二进制是11111111-27是11100101。如果你不理解补码规则直接把-27用除2取余法处理会得到一堆奇怪的取模结果。实际操作时建议先弄清位宽8位、16位、32位再按补码规则换算。补码的速记方法是正数原码直接写负数用对应正数原码按位取反再加1。比如先写出27对应的二进制00011011按位取反得到11100100再加1就是11100101这就是-27的8位补码。再次是浮点精度问题。前面已经提过0.1转二进制是个无限循环实际存储时按IEEE 754标准在1位符号、8位指数、23位尾数的框架下截断。这就导致任何浮点数的进制转换都不能用简单“乘2取整”精确完成理论上总会丢失精度。这也是为什么金融计算不能用浮点数做金额运算而是用整数分的表示法或者使用专门的十进制库本质上都是绕开二进制对某些十进制小数的不可精确表示问题。5.3 面试和笔试里的进制转换高频题进制转换是面试基础题里的常客考察的是位运算基本功和边界条件处理。常见题型有这么几类第一类是手写转换函数。要求不能用内置函数实现十进制转二进制或反过来。这类题目必考状态就是0和1的边界必须确保0返回正确并且不会死循环。第二类是统计二进制中1的个数。经典解法是while n: count n 1; n 1还有更巧妙的n (n-1)清零最低位1的技巧。后面这个解法背后的道理是n-1会把最低的1变成0并把右边的0全部变成1再与n相与会把这个位置及右边的所有位清零所以每次循环都会消掉一个1。第三类是判断一个数是否是2的幂。这类题的实质就是检查二进制表示中是否只有一个1用n (n-1) 0就能一次判断。这个判断方式比反复除2快得多。第四类是大整数转换。很多人手写转换函数只测两位数用循环会时间很长。应对策略是别一位一位操作而是用分组的思路把整数按四位二进制一组换十六进制、再整体换算速度能快很多。结合以上这些建议平时训练时用小而无害的数值反复验证例如27、182、0.625这种非对称数字比用8、16、64这类全是2的幂的“假一般情况”更能暴露问题。写在最后的几点个人体会接触底层开发这些年我用过十六进制解析协议、用位运算压缩状态、用补码调试过传感器数据但回头看最基础、最频繁被用到的还是十进制与二进制的这种互转能力。它看起来简单做对很难难在细碎难在边界难在大家都觉得会所以不去查证。我自己的习惯是任何一次转换之后都花十秒做反向验算遇到特殊值单独写注释说明二进制表示方式在代码里永远用明确注释标出哪个是最低位、哪个是符号位。长期坚持下来因为进制问题导致的bug明显少了很多。如果这篇文章能帮你在某个需要读状态寄存器的深夜少一点迷茫那我这个记录就没白写。进制转换这事儿不算高深但它值得你花半小时彻底吃透之后一路都能走得顺一些。