十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

二进制与十进制互转全解析:整数、小数、负数与补码

二进制与十进制互转全解析:整数、小数、负数与补码 二进制和十进制之间的互转几乎是每个接触计算机的人绕不开的第一道门槛。有人觉得这东西太基础不值得单独拿出来讲但我带过不少刚入行的朋友发现真正能在脑子里快速完成转换、并且理解每一步为什么这么做的人其实并不多。大多数人停留在背公式的阶段——除2取余、乘2取整考试能写出来可一旦遇到负数、小数、或者需要口算校验的场景立刻就卡壳了。这篇内容就是想把二进制与十进制互转这件事彻底讲透从最朴素的原理出发把整数、小数、负数三条线全部打通再补上一些实际工作中真正用得上的技巧和容易踩的坑。不管你是刚学编程的新手还是工作几年但一直没系统梳理过这块知识的老手看完应该都能有新的收获。1. 为什么二进制和十进制互转值得单独拿出来讲1.1 一个被低估的基础能力很多人对进制转换的态度是知道就行反正有计算器。这话在大部分日常开发里没错但有几个场景是计算器帮不了你的。比如你在读一段内存dump看到一串十六进制数据想快速判断某个字节的高位是不是符号位比如你在调一个位运算的bug需要验证某个掩码操作之后的结果对不对再比如面试的时候面试官让你口算一个十进制数转二进制你总不能掏手机。这些场景的共同点是你需要的是理解而不是工具。进制转换的本质是理解数字在不同基底下的表示方式。十进制是我们从小熟悉的每一位的权重是10的幂二进制每一位的权重是2的幂。所谓转换就是把同一个数量用不同的权重体系重新表达一遍。这个理解一旦建立起来后面所有的技巧都是自然推导出来的而不是死记硬背的。1.2 从会算到算得快的差距在哪我见过两种人。一种人拿到一个十进制数老老实实除以2记余数一直除到0然后把余数倒过来写。这个方法绝对正确但速度慢而且除的次数多了容易出错。另一种人看到217脑子里几乎是瞬间反应出11011001。差距不在于天赋而在于后者掌握了几个关键的锚点和拆分技巧。举个例子2的幂次方是必须刻在脑子里的1、2、4、8、16、32、64、128、256、512、1024。这几个数字就像十进制里的10、100、1000一样是二进制世界的整数关口。当你看到217第一反应应该是它比128大、比256小所以最高位是第8位从0开始数就是第7位。217减128等于8989比64大所以第6位是1减完剩25。25比16大第4位是1剩9。9等于8加1所以第3位和第0位是1。拼起来就是11011001。整个过程心算几秒钟比列竖式除法快得多。1.3 这篇内容覆盖的范围下面我会分几条线来讲。先是十进制整数转二进制重点讲拆分法和除法两种思路各自的适用场景然后是二进制转十进制讲按权重展开和分组速算接着是小数部分这块是很多人真正的盲区包括精度限制和舍入问题再往后是负数的表示原码、反码、补码这条线必须理清楚否则位运算永远学不明白最后会聊一些实际工作中遇到的坑比如C语言里用strstr查找二进制内存为什么不可靠、不同CPU架构下二进制包的差异等等。每一条线我都会给出可复现的步骤和验证方法你可以跟着算一遍。2. 十进制整数转二进制拆分法与除法的取舍2.1 除2取余法的完整流程与它的局限先把这个最经典的方法说清楚因为它是所有教材的起点也是理解原理的基础。方法是把十进制数不断除以2每次记录余数直到商为0然后把所有余数从下往上读出来。以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。验证一下128641681 217正确。这个方法的优点是逻辑简单、不需要记任何东西任何十进制数都能机械地算出来。但它的局限也很明显除的次数等于二进制的位数。一个32位的整数最坏情况下要除32次手算极易出错而且中间任何一步算错后面全错。所以它适合用来理解原理或者写程序实现但不适合心算。2.2 拆分法利用2的幂次快速定位拆分法的核心思想是任何一个十进制整数都可以表示成若干个2的幂之和。你要做的就是从大到小看这个数能装下哪些2的幂。还是217。先把常用的2的幂列出来2的幂值2^71282^6642^5322^4162^382^242^122^01从128开始217 ≥ 128取1剩89。89 ≥ 64取1剩25。25 32取0。25 ≥ 16取1剩9。9 ≥ 8取1剩1。1 4取0。1 2取0。1 ≥ 1取1剩0。结果11011001。和除法得到的一致。拆分法的好处是你只需要做减法和比较不需要做除法心算负担小很多。而且一旦你熟悉了2的幂整个过程可以在几秒内完成。它的缺点是必须记住2的幂次表但这个表其实很好记因为它是翻倍的1、2、4、8、16、32、64、128、256、512、1024、2048、4096……记住到4096基本就够日常用了。2.3 两种方法的适用场景对比那什么时候用哪种我的经验是这样数字小于256直接用拆分法心算最快。数字在256到65535之间拆分法仍然可用但建议先拆成高字节和低字节分别处理比如0x1234这种高字节0x1218低字节0x3452分别转二进制再拼起来。数字很大或者需要程序处理用除法因为除法可以写成循环不容易出错。需要验证结果两种方法交叉验证如果结果一致基本可以确认无误。提示拆分法的一个常见错误是跳位。比如算到剩9的时候有人会直接写1001但9对应的是81也就是第3位和第0位中间的第2位4和第1位2都是0所以是1001没错。但如果剩的是5那就是41即101第1位是0不能漏。2.4 一个实用的心算技巧从高位往低位填还有一个我自己常用的技巧叫从高位填。先确定这个数需要多少位然后从最高位开始每一位判断如果这一位是1剩下的数够不够表示。比如217最高位是128第7位因为217 256。那么从第7位开始第7位217 ≥ 128是填1剩89。第6位89 ≥ 64是填1剩25。第5位25 ≥ 32否填0。第4位25 ≥ 16是填1剩9。第3位9 ≥ 8是填1剩1。第2位1 ≥ 4否填0。第1位1 ≥ 2否填0。第0位1 ≥ 1是填1剩0。这个方法的好处是你不需要预先知道结果有多少位只要从可能的最大位开始往下判断就行。而且每一步的判断都很简单就是当前剩余的数够不够减。3. 二进制转十进制按权重展开与分组速算3.1 按权重展开最不容易出错的方法二进制转十进制最稳妥的方法就是按权重展开。每一位乘以它对应的2的幂然后全部加起来。比如110110011×2^7 128 1×2^6 64 0×2^5 0 1×2^4 16 1×2^3 8 0×2^2 0 0×2^1 0 1×2^0 1加起来128641681 217。这个方法绝对不会错但位数多了会加得很累。所以实际用的时候我一般会先把所有为1的位对应的值列出来再相加而不是逐位乘。因为0乘任何数都是0没必要算。3.2 分组速算把长二进制串切成小块当二进制串很长的时候比如一个32位的数直接展开加很容易加错。这时候可以用分组法。常见的是4位一组对应十六进制或者8位一组对应一个字节。4位一组的好处是每一组刚好对应一个十六进制数字而十六进制和十进制的对应关系是固定的可以背下来二进制十六进制十进制0000000001110010220011330100440101550110660111771000881001991010A101011B111100C121101D131110E141111F15有了这张表你可以先把二进制转成十六进制再把十六进制转成十进制。比如11011001从右往左每4位一组1101和1001。1101是D131001是9。所以十六进制是0xD9。然后13×169 2089 217。这个方法在位数多的时候特别快因为十六进制转十进制只需要做一次乘16加余数。3.3 从右往左标位号避免位权数错一个非常常见的错误是位权数错。比如把1101当成1×2^31×2^20×2^11×2^0这没错但如果从左边开始数位号就会全错。所以一定要养成习惯最右边那一位永远是第0位位权是2^01。我自己的做法是拿到一个二进制串先在草稿纸上从右往左标上0、1、2、3……然后再对应权重。虽然多花几秒钟但能避免大错。尤其是位数多的时候这个习惯能救命。3.4 验证技巧用奇偶性快速检查有一个很简单的检查方法二进制的最低位最右边那一位如果是1这个数就是奇数如果是0就是偶数。因为除了2^01之外其他所有2的幂都是偶数偶数之和还是偶数所以奇偶性完全由最低位决定。比如11011001最低位是1所以是奇数217确实是奇数。如果算出来是偶数那肯定错了。这个检查只需要一秒钟但能抓住很多低级错误。4. 小数部分的转换精度限制与舍入问题4.1 十进制小数转二进制乘2取整整数部分用除法小数部分用乘法。方法是把小数部分不断乘以2每次取整数部分作为二进制位直到小数部分为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.625的二进制是0.101。验证1×2^-1 0×2^-2 1×2^-3 0.5 0 0.125 0.625正确。4.2 为什么0.6转二进制永远转不完现在试0.60.6 × 2 1.2 → 取1剩0.2 0.2 × 2 0.4 → 取0剩0.4 0.4 × 2 0.8 → 取0剩0.8 0.8 × 2 1.6 → 取1剩0.6 0.6 × 2 1.2 → 取1剩0.2 ...你会发现从0.6开始经过4步又回到了0.6进入了循环。所以0.6的二进制是0.100110011001...无限循环。这就是为什么0.6在计算机里无法精确表示。这个现象的本质是十进制小数要能用有限位二进制精确表示它的分母最简分数形式必须是2的幂。0.625 5/8分母是82^3所以能精确表示。0.6 3/5分母是5不是2的幂所以不能精确表示。4.3 精度限制下的舍入策略既然有些小数无法精确表示那计算机怎么处理答案是舍入。IEEE 754浮点数标准定义了多种舍入模式最常用的是就近舍入偶数优先round to nearest, ties to even。什么意思假设你要保留3位小数而第4位是1后面还有非零位那就进位如果第4位是1但后面全是0那就看第3位如果是奇数就进位偶数就不进。这样做的目的是让舍入误差在统计上更均衡。实际编程中你不需要手动实现这个但你需要知道浮点数比较不能用。因为0.10.2在大多数语言里不等于0.3而是等于0.30000000000000004。正确的做法是判断两个数的差的绝对值是否小于某个很小的阈值epsilon。注意涉及金额计算时绝对不要用浮点数。应该用整数以分为单位或者十进制库如Python的decimal、Java的BigDecimal。这是血泪教训我见过太多因为浮点误差导致对账差几分钱的案例。4.4 二进制小数转十进制负幂展开反过来二进制小数转十进制就是按负幂展开。比如0.1011×2^-1 0.5 0×2^-2 0 1×2^-3 0.125加起来0.625。这个方法同样适用于无限循环的情况但通常只取有限位近似。5. 负数的二进制表示原码、反码、补码5.1 为什么需要补码如果只用原码表示负数比如用最高位做符号位0表示正1表示负那么会出现两个问题一是0有两种表示0和-0二是加减法需要分别处理符号电路设计复杂。补码解决了这两个问题。补码的定义是正数的补码等于原码负数的补码等于其绝对值的原码取反加1。这样减法可以统一用加法实现而且0只有一种表示。5.2 从原码到补码的完整推导以8位为例-5的表示5的原码00000101取反11111010加111111011所以-5的补码是11111011。验证11111011作为无符号数是251而251 256 - 5正好是-5在模256下的表示。这个模的概念很重要。8位二进制能表示256个数从0到255。补码把一半分给负数一半分给正数范围是-128到127。所以-5就对应251因为2515256溢出后归零。5.3 补码转十进制的两种方法方法一如果最高位是1说明是负数。先减1再取反得到绝对值然后加负号。比如11111011减1得11111010取反得00000101即5所以是-5。方法二直接按权重展开但最高位的权重是负的。对于8位最高位权重是-2^7-128。所以11111011 -128 64 32 16 8 0 2 1 -128 123 -5。这个方法更快但需要记住最高位是负权重。5.4 实际编程中的注意事项在C语言里有符号整数溢出是未定义行为UB编译器可以做任何假设。所以不要依赖溢出来做判断。另外右移有符号负数时不同编译器的行为可能不同算术右移还是逻辑右移标准规定是实现定义。所以如果你要处理位运算建议统一用无符号类型避免这些坑。还有一个常见问题把负数转成二进制字符串时很多人直接用除2取余结果得到的是补码的二进制但符号位没有单独处理。如果你想要的是-101这种带符号的表示需要先取绝对值转换后再加负号。6. 实际工作中容易踩的坑与验证方法6.1 C语言strstr不能用于二进制内存热词里提到了c语言strstr()能否用于查找二进制内存答案是不能。strstr是按字符串处理的它依赖\0作为结束符。二进制内存里随时可能出现0x00字节strstr会在第一个0x00处停止导致查找不完整或者越界。正确的做法是用memmemGNU扩展或者自己写循环用memcmp逐字节比较。6.2 不同CPU架构的二进制包不通用热词里还有下载适配你平板cpu架构的memtester二进制包(arm/arm64版本)。这提醒我们二进制可执行文件是和CPU指令集绑定的。ARM架构的二进制不能在x86上运行反之亦然。即使是ARMarmv7和arm64也不完全兼容。所以下载二进制包时一定要确认目标平台的架构。用uname -m可以查看当前架构。6.3 用计算器验证你的手算结果手算完之后一定要验证。最简单的方法是用系统自带的计算器程序员模式或者用Pythonprint(bin(217)) # 0b11011001 print(int(11011001, 2)) # 217 print(hex(217)) # 0xd9养成验证的习惯能帮你快速发现错误也能加深对转换过程的理解。6.4 一个综合练习217的完整转换链路最后用一个完整的例子把前面讲的串起来。217十进制转二进制11011001二进制转十六进制0xD9十六进制转十进制13×169217如果是8位有符号数11011001最高位是1是负数值为-39因为-12889-39如果是无符号数就是217同一个二进制串在不同解释下值不同。这就是为什么类型和位宽这么重要。进制转换这件事说到底就是理解权重和模这两个概念。权重决定了每一位代表多少模决定了负数和溢出的行为。把这两个概念吃透剩下的都是熟练度问题。我自己的经验是每天花五分钟随机抽几个数练一练一周之后心算速度会有明显提升。另外写代码的时候多用bin()、hex()这些函数验证慢慢就形成肌肉记忆了。
返回列表