1. 为什么值得花时间把数制转换彻底搞明白刚入行那几年我一直觉得数制转换是学校里应付考试的东西工作中用到再查一下就行。直到有次调试一个传感器驱动数据手册上写着寄存器地址0x4002 3800偏移量0x1C而我在代码里用十进制写偏移量怎么算都不对白白折腾了一下午。那次之后我才真正把二进制、八进制、十进制、十六进制这套东西系统梳理了一遍。数制本质上就是同一堆数量的不同表达方式。十进制之所以成为日常默认是因为人有十根手指计算机用二进制是因为电路只有通和断两种稳定状态八进制和十六进制被发明出来纯粹是因为二进制写起来太长人眼扫过去容易数错。理解这一点后面的转换就不再是死记硬背而是顺理成章的换算。这篇文章主要面向三类人一是刚接触编程或者嵌入式的新手看到0x、0b就发怵二是做网络、运维、逆向的朋友经常要在不同进制之间来回跳三是准备考试或者面试需要把转换过程讲清楚的人。我会把原理、手算方法、代码实现、常见坑都过一遍尽量做到看完就能上手。文中涉及参数和步骤的地方我会把推导过程写出来而不是只丢一个结论。2. 数制的本质位权、基数与表示法2.1 位权到底是什么一个生活化的类比先说一个最核心的概念位权。你手里有一叠钱面额分别是 100、10、1那数字 345 就表示 3 张 100、4 张 10、5 张 1加起来 345。这里的 100、10、1 就是位权而 10 是基数因为每往左一位代表的量就乘以 10。计算机的世界里基数变成了 2。二进制数字 1011从右往左的位权依次是 1、2、4、8所以它等于 1×1 1×2 0×4 1×8 11。这个乘基数、加结果的动作就是所有进制转十进制的基本方法没有例外。把这个思路推广开八进制基数 8位权是 8 的幂1、8、64、512……十进制基数 10位权是 10 的幂1、10、100、1000……十六进制基数 16位权是 16 的幂1、16、256、4096……提示位权是理解一切进制转换的钥匙。凡是搞不清某个数怎么来的先把它每一位的位权列出来问题基本就迎刃而解。2.2 基数决定了几套符号够用基数决定了这个进制需要多少个不同的数字符号。十进制需要 0 到 9 共十个符号够用。二进制只要 0 和 1 两个。八进制要 0 到 7 八个。十六进制最特殊需要十六个符号可阿拉伯数字只有 0 到 9 十个怎么办于是借用了字母 A 到 F分别代表 10、11、12、13、14、15。这也是为什么你在代码里经常看到0xFF这种写法F 就是 15两个 F 拼起来就是 255。刚接触的人容易卡在A 为什么等于 10其实没有为什么就是约定就跟扑克牌里 J 代表 11 一样用起来方便而已。不同语言和工具对进制的书写约定大同小异下面这张表建议存一下进制常见前缀示例典型场景二进制0b0b1010位运算、掩码、寄存器标志位八进制0或0o0o17或017Linux 文件权限、早期 C 代码十进制无255日常计算、业务逻辑十六进制0x0xFF内存地址、颜色值、字节流需要特别留个心眼的是八进制。C 语言里以0开头的数字会被当成八进制所以int x 010;里 x 实际是 8不是 10。这个坑我踩过当时排查一个数组下标越界最后发现是某个常量前面手滑多了个零。2.3 十六进制为什么和二进制是亲兄弟十六进制在计算机领域地位特殊原因就一个16 是 2 的 4 次方。这意味着每一个十六进制位刚好对应四个二进制位不多不少。具体对应关系是固定的背下来收益极高十六进制二进制十六进制二进制0000081000100019100120010A101030011B101140100C110050101D110160110E111070111F1111有了这张表二进制和十六进制之间的转换就变成了四换一、一拆四的拼图游戏完全不需要做乘法和除法。比如1101 1010直接对着表拆成D和A结果就是0xDA。反过来0x3F7拆成3F7各自展开成0011 1111 0111就行了。八进制同理只不过它是 2 的 3 次方所以是三位一组。这也是为什么八进制在现代编程里用得越来越少——四位一组比三位一组更贴合字节的边界一个字节 8 位刚好两个十六进制位。3. 四类进制的核心转换方法全拆解3.1 任意进制转十进制按位权展开求和这是最基础也最通用的方法。把每一位数字乘以它对应的位权然后全部加起来。以二进制11010为例从右往左标位权位数 1 1 0 1 0 位权 16 8 4 2 1 计算 1×16 1×8 0×4 1×2 0×1 16 8 2 26八进制237转十进制7×1 3×8 2×64 7 24 128 159十六进制2AF转十进制注意 A10、F15F×1 A×16 2×256 15 160 512 687这套方法对小数部分同样适用只不过位权变成负幂次。比如二进制0.1011×2⁻¹ 0×2⁻² 1×2⁻³ 0.5 0 0.125 0.625注意手算时遇到很长的数建议从右往左数把位权写在数字上方这样不容易错位。错位是手算最常见的失误一旦某一位偏了后面全错。3.2 十进制转其他进制整数除基取余小数乘基取整整数部分用除基取余、逆序排列。以十进制 156 转二进制为例156 ÷ 2 78 ... 余 0 ← 最低位 78 ÷ 2 39 ... 余 0 39 ÷ 2 19 ... 余 1 19 ÷ 2 9 ... 余 1 9 ÷ 2 4 ... 余 1 4 ÷ 2 2 ... 余 0 2 ÷ 2 1 ... 余 0 1 ÷ 2 0 ... 余 1 ← 最高位把余数从下往上读10011100。验证一下1281684156对的。转八进制和十六进制思路完全一样只是除数换成 8 和 16。156 转十六进制156 ÷ 16 9 ... 余 12C 9 ÷ 16 0 ... 余 9逆序读是9C验证9×1612156。小数部分则相反用乘基取整、顺序排列。以十进制 0.6875 转二进制为例0.6875 × 2 1.375 → 取整 1保留 0.375 0.375 × 2 0.75 → 取整 0保留 0.75 0.75 × 2 1.5 → 取整 1保留 0.5 0.5 × 2 1.0 → 取整 1保留 0取整结果按顺序读0.1011。验证0.50.1250.06250.6875吻合。这里就要说到那个热搜问题了:十进制小数转换为二进制有精度限制时需要考虑舍入吗答案是必须考虑而且这是很多人踩过的真实坑。因为二进制只能精确表示那些分母是 2 的幂次的分数像 0.1、0.2、0.3 这种十进制小数转成二进制是无限循环的。用有限位数表示就必然要截断或者舍入。举个实际例子很多语言里0.1 0.2 ! 0.3就是因为这个。如果你在做金额计算、科学计算这类对精度敏感的场景别用浮点数改用整数以分为单位或者专门的十进制库比如 Python 的decimal、Java 的BigDecimal。至于舍入方式常见的有四舍五入、银行家舍入round half to even、向上/向下取整金融场景优先考虑银行家舍入能减少系统性偏差。3.3 二进制与八进制、十六进制的快捷转换前面说过十六进制一位对四位八进制一位对三位。利用这个特性可以跳过十进制直接互转速度飞快。二进制转十六进制从小数点开始往左往右各四位一组不足补零。二进制 101 1010 1111 分组 0101 1010 1111 最左边不足四位补个零 结果 5 A F → 0x5AF二进制转八进制同样分组但是三位一组。二进制 101101011 分组 101 101 011 从右往左三位一组 结果 5 5 3 → 0o553提示最左边和最右边不足位时补零补的是数值位方向的零不要补错位置。左边补零不改变数值右边补零会改变数值所以小数部分右侧补零要慎重——实际上转换时小数部分是从左往右分组不足的在最右补零这是为了凑齐分组数值上是等价的。3.4 负数与补码绕不开的一道坎热搜里负数的二进制二进制补码出现频率很高说明这是大家的痛点。计算机里表示负数主流方案是补码。以 8 位为例正数 5 是0000 0101。要得到 -5步骤是先取反得到1111 1010再加 1 得到1111 1011。这个1111 1011就是 -5 的补码表示。为什么要这么麻烦因为用补码可以让减法和加法共用一套电路。5 (-5)用补码算0000 0101 1111 1011 ----------- 1 0000 0000 ← 最高位的进位溢出丢弃结果是 0完美。如果不用补码用原码减法电路和加法电路就得分开设计硬件成本高得多。补码还有个好处是 0 只有一种表示而原码和反码里 0 和 -0 是两种判断起来麻烦。实操中要注意的是溢出。8 位有符号数范围是 -128 到 127129 就溢出了。当你看到1000 0000这个值如果是无符号就是 128有符号就是 -128临界点要格外小心。4. 代码和工具里的数制转换实操4.1 用 Python 和 C 快速验证转换结果Python 内置了转换函数调试时非常方便# 十进制转其他进制 print(bin(156)) # 0b10011100 print(oct(156)) # 0o234 print(hex(156)) # 0x9c # 其他进制转十进制 print(int(10011100, 2)) # 156 print(int(234, 8)) # 156 print(int(9C, 16)) # 156 # 处理小数 from decimal import Decimal, getcontext getcontext().prec 30 print(Decimal(0.1)) # 展示浮点数的真实存储值C 语言里printf的格式说明符可以直接输出不同进制#include stdio.h int main() { int n 156; printf(十进制: %d\n, n); // 156 printf(八进制: %o\n, n); // 234 printf(十六进制: %x\n, n); // 9c printf(十六进制大写: %X\n, n); // 9C return 0; }这里有个细节printf没有二进制格式符因为 C 标准没纳入。要输出二进制得自己写循环或者用非标准扩展。另外%o输出八进制时前面不会自动加0%#o才会加%#x会加0x。注意C 里scanf读十六进制用%x但如果输入超过int范围结果未定义。处理大数要用strtoul之类的函数并检查errno。4.2 十六进制编辑器和二进制分析场景做逆向或者排查文件格式问题时十六进制编辑器是必备工具。像 HxD 这类工具打开任意文件都能看到每个字节的十六进制值右侧配上对应的 ASCII 字符显示。举个例子PNG 文件开头固定是89 50 4E 47 0D 0A 1A 0A。其中50 4E 47对应 ASCII 的 PNG89是高位字节用来检测传输过程有没有被错误处理成文本模式。看懂这些你就能快速判断一个文件是不是伪装了扩展名。CTF 里常见的二进制题目比如栈溢出、rip控制这类本质也是要计算出精确的偏移量和地址。这些地址在反汇编器里都是十六进制你要能快速把十六进制地址和内存布局对应起来。字节序大端小端也是这里的关键0x12345678在小端机器上内存里是78 56 34 12顺序反的。4.3 数据库和其他工具中的进制玩法通达信这类软件里十六进制代码有时会出现在数据文件或者公式编码里。处理这类数据时关键是把十六进制字节流正确地解码成有意义的数值或字符串注意编码和字节序。容器运维场景里也会遇到二进制相关的东西比如某些组件的二进制分发包部署用 systemd 管理时监听本地 socket 文件。这些路径配置在服务单元里通常以绝对路径给出排查连接问题时要确认进程实际监听的路径和权限是否符合预期。颜色领域更是十六进制的天下。网页和设计工具里#RRGGBB是标配前两位是红、中间两位是绿、后两位是蓝每个通道 0 到 255。像陶土白这种色号查出来的 hex 值就是设计师和前端之间交接的通用语言。理解十六进制的位权你就能心算出不同颜色值的亮度大概在什么区间。5. 常见问题速查与避坑经验5.1 数制转换常见问题排查表实际工作中遇到的问题很多是细节没处理干净。我把最常见的几类整理成表方便对照问题现象可能原因排查方法解决方法数值大了一倍误把八进制当十进制检查前缀是否有前导 0去掉前导零或改成十进制负数结果异常忽略了补码表示确认是有符号还是无符号明确类型和位宽小数转换不精确二进制无法精确表示用高精度库观察真实值改用整数或十进制库十六进制地址算错位权错位或字节序搞反逐位列出位权重新算确认大小端重新分组颜色值对不上遗漏 # 或长度不对检查是否为 6 位完整格式补齐六位区分大小写5.2 手算提速的几个实用技巧第一个技巧是先转十六进制再转二进制。比如要算十进制 5000 的二进制直接除 2 要算十几次但如果先算出十六进制1388再一位拆四位快得多也不容易错。第二个技巧是记住几个常用位权值。二的 1 到 16 次方背下来转换时可以直接套2、4、8、16、32、64、128、256、512、1024、2048、4096、8192、16384、32768、65536。这几个数在编程里出现频率极高尤其 1024、65536 这类看到就条件反射。第三个技巧是善用分组。超过 10 位的二进制数用人眼直接读很容易数错从右往左每四位加个空格或者分隔符瞬间清晰。很多编辑器和终端也支持高亮显示分组用起来很顺手。5.3 那些文档里不会写的坑坑一八进制的权限表示。Linux 里chmod 755里的 755 是八进制7 表示读写执行4215 表示读执行41。如果你用十进制思维理解 755算出来完全不对。这种场景下理解数制不是学术问题是直接关系到权限是否配置正确。坑二浮点比较别用等号。前面说过 0.1 的二进制表示有误差所以0.1 0.2 0.3在某些语言里返回 false。正确做法是比较绝对差值是否小于一个极小的阈值比如abs(a - b) 1e-9。金融计算则干脆不要用浮点。坑三位运算的符号扩展。对负数做移位或者与运算时有符号数的符号位会扩展。比如-1 1在很多语言里还是 -1而不是一个大正数。做位掩码时优先用无符号类型避免符号位捣乱。坑四字符串和二进制数据的边界。像 C 里的strstr()这类字符串函数是用来处理以\0结尾的文本的中间一旦出现\0就会提前截断。二进制内存里随时可能包含\0所以查找二进制数据要用memmem或者带长度的比较函数不能直接套字符串函数。这个坑在做协议解析时特别容易翻车。坑五字节序的隐蔽性。网络协议规定用大端x86 是小端。当你手工拼接多字节数值时如果不做转换得到的会是反的。十六进制看起来都是一样的字符但在内存里的排列顺序才是真相。6. 硬件与进阶场景里的数制应用6.1 键盘扫描与编码中的十六进制用 Verilog HDL 设计一个十六进制键盘的扫描和编码电路这个场景把数制用到了硬件层面。矩阵键盘通常是 4 行 4 列扫描时逐行拉低读列线状态判断哪个键被按下。十六进制键盘有 16 个键从 0 到 F每个键对应一个唯一的四位二进制编码。比如按下 A编码器输出1010。这个输出可以直接用于后续的显示驱动或者数据总线。设计要点在于消抖。机械按键按下和释放时会有几十毫秒的抖动扫描电路需要用计数器做延迟确认或者用移位寄存器做状态过滤。如果扫描频率太高又没消抖一个按键会被识别成多次这是新手常见的问题。另外编码器的优先级也要考虑。如果多个键同时按下得有明确的优先级规则通常按矩阵位置低位优先或者高位优先来确定输出。这是组合逻辑设计中容易忽略的细节。6.2 从数制角度看数据存储与传输磁盘、内存、网络底层都是字节流。理解数制让你能看见这些字节。比如一个 32 位整数0x01020304在内存里按小端存储是04 03 02 01。你在十六进制编辑器里看到的就是这个顺序。网络抓包时协议头的字段长度、标志位都定义到比特级别。TCP 头里那些标志位SYN、ACK、FIN各占一位本质上是同一个字节里不同位置上的二进制位。理解位权你就能自己算出标志位的组合值也能从十六进制值反推出哪些标志位被置位了。机器学习和人工智能领域也离不开数制。模型权重常见的是 FP16、BF16 这类浮点格式它们的指数位和尾数位分配不一样直接影响到模型的精度和显存占用。BF16 指数位和 FP32 一样是 8 位尾数只留 7 位牺牲精度换范围FP16 指数 5 位、尾数 10 位范围小但精度高一些。选择哪种格式本身就是数字表示原理在实际工程里的取舍。7. 写在最后的一点个人体会数制转换这东西看起来简单但要真正用得顺靠的是反复练和踩坑。我的建议是不要只背结论而是每次遇到一个进制问题都花三十秒在心里过一遍位权时间长了就成了本能反应。具体练习方式上可以每天随手拿几个数做转换比如看看时间戳的十六进制看看文件大小的二进制坚持一两周基本就刻进肌肉记忆了。工具方面手头常备一个十六进制编辑器和一个带多进制显示的计算器遇到不确定的立刻验证别靠猜。另外提醒一句涉及金额、坐标、传感器读数这类场景一定要在动手前想清楚用什么数据类型和精度。数制选择错一次排查成本可能远超预期。这是我最想传达的一条经验。
阅读完成 · 觉得有帮助?