news 2026/10/2 5:13:30

小数二进制与十六进制转换:从0.1+0.2精度误差到调试工具实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
小数二进制与十六进制转换:从0.1+0.2精度误差到调试工具实战

你肯定在代码里撞过0.1 + 0.2 != 0.3这种邪门事件,也肯定在调试器里见过0x3f800000这种读起来像乱码的十六进制数字。这两件事表面看八竿子打不着,实际上背后是同一个基础问题:带小数的数字,在计算机里到底是怎么用二进制存储的,又为什么偏偏用十六进制展示给人类看。这篇文章就从“小数”这个角度切入,把十进制小数转二进制、二进制转十六进制的完整方法、手算步骤、误差边界,以及常见的调试工具用法一次性讲清楚。不管你是刚学进制转换的学生,还是被浮点数精度坑过的开发,这篇文章都能给你一套能直接拿去用的思路。

1. 先把整数转换讲透:二进制和十六进制的基础心法

小数转换之所以容易卡壳,往往是因为整数部分就没形成肌肉记忆。所以咱们先花一小段把整数的二进制和十六进制彻底捋顺,后面处理“小数点右边”的内容才会有参照物。

1.1 整数转二进制:除2取余其实是在拆秤砣

十进制整数转二进制,教科书上写的是“除2取余,倒序排列”。这个口诀背起来容易,但很多人不知道它背后的直觉是什么。我的理解是:二进制数字每一位代表一个固定的“秤砣”,从右往左依次是 1、2、4、8、16……你要做的就是用这套砝码去“凑出”目标数字,能凑上就在这一位写1,凑不上就写0。

除2取余的本质,就是在判断这个数里包含了多少个“1”、多少个“2”、多少个“4”。比如十进制13:

  • 13 ÷ 2 = 6余1,说明你拆出了一个1;
  • 6 ÷ 2 = 3余0,说明这个位置没有2;
  • 3 ÷ 2 = 1余1,说明这里有4;
  • 1 ÷ 2 = 0余1,说明还有8。

把余数从下往上倒着写,就是1101。验证一下:8 + 4 + 0 + 1 = 13,完全正确。

值得提醒的是,网上有些教程会把这一步写反,最后得出1011,那就是漏了“倒序排列”这个动作。我自己当年初学时就栽在这个坑里,心算很快但顺序错得离谱。如果你也容易迷糊,可以反过来用加法验证一遍:从左往右每一位的权重是 8、4、2、1,算出来必须等于原数字。

1.2 整数转十六进制:四位二进制拼成一位十六进制

整数转十六进制最省事的路径,不是直接除16取余,而是先转成二进制,再从右往左每四位一组。因为16正好是2的4次方,一位十六进制数对应四位二进制数,这层关系是天然绑定的。

我列一张4位二进制到十六进制的对照表,你后面频繁用,建议直接刻进脑子:

二进制十六进制十进制
000000
000111
001022
001133
010044
010155
011066
011177
100088
100199
1010A10
1011B11
1100C12
1101D13
1110E14
1111F15

举个例子,刚才算的二进制1101,从右往左刚好四位,查表就是D,所以十进制13的十六进制就是0xD。再看大一点的数,比如十进制255,二进制是11111111,从右往左四位一拆:1111 1111,写着就是0xFF。你马上能发现十六进制的优势:4位二进制数字压缩成1位十六进制,打印出来短了一半都不止,可读性瞬间提升。这也是为什么内存调试、文件分析、网络报文这些场景,清一色用十六进制做展示格式。

2. 小数转二进制:乘2取整与“算不干净”的真相

整数解决了,现在攻克核心难点:小数点右边那部分怎么转二进制。

2.1 小数部分的手算流程

小数部分转二进制,口诀是“乘2取整,顺序排列”。每乘一次2,把整数位摘下来记着,剩下的小数部分继续乘。举个例子,十进制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,结束。

把取出来的整数位从上往下写:0.101。验算一下:1×1/2 + 0×1/4 + 1×1/8 = 0.5 + 0 + 0.125 = 0.625,完全对得上。

所以二进制小数每一位的意义,就是二分之一、四分之一、八分之一……这些“秤砣”的累加。和整数部分不同,小数部分的秤砣是从左往右依次减半的,方向不能搞混。

2.2 为什么0.1在二进制里永远算不干净

0.625能算干净,是因为它恰好能由 1/2 和 1/8 组合出来。但换成十进制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……

看到没,到第6步开始循环了。后面会出现0.4 → 0.8 → 1.6 → 0.6 → 1.2 → 0.4的无限循环,二进制下0.1是一个无限循环小数:0.0001100110011...。

这个现象的原因,本质上是“进制权重不同”。十进制能用十分之一、百分之一凑出0.1,二进制只有二分之一、四分之一、八分之一这些砝码,你永远没法用有限的 1/2 的幂组合精确拼出0.1。你想想生活中用杆秤,如果秤杆上只有 1/2、1/4、1/8 这些刻度,想称出 0.1 斤东西,也只能无限逼近,道理完全一样。

2.3 精度限制时到底该不该舍入

很多热词都在讨论“十进制小数转换为二进制有精度限制时,需要考虑舍入吗”。结论是:需要,而且舍入策略会直接影响结果。

计算机里浮点数不可能存无限位,IEEE 754 单精度 float 尾数只有 23 位,双精度 double 尾数 52 位。超出位数的部分就必须处理,常见策略有:就近舍入、朝0舍入、向上舍入、向下舍入。平时默认用的“就近舍入”和我们学数学的四舍五入类似,但二进制下它是“舍到最近的偶数”,这么做是为了让大量数据的累计误差尽量均衡。

举一个很典型的例子,十进制0.1转成双精度二进制时,尾数被截断为某个 52 位近似值,这个值比真正的0.1稍微大了一点点。所以你在 Python 里执行0.1 + 0.2,得到的是0.30000000000000004,而不是干净利落的0.3。这不是计算机算错了,而是它在用二进制“近似”0.1 时的副作用。

实操上我的建议是:做金额、计时这类不允许误差的开发时,别直接用二进制浮点数,要么用整数按“分”存储,要么用Decimal这类十进制定点数库。做科学计算有误差预算,那再用浮点不迟。

3. 小数从二进制到十六进制:分组的顺序是关键

二进制小数会很长,比如0.1的完整双精度二进制写出来一遍根本记不住。这时十六进制就该登场了。小数的二进制转十六进制,方法仍然是“四位一组”,但方向要特别注意。

3.1 十六进制的优势:四位对一个,人脑可直接换算

为什么调试工具偏爱十六进制?因为一个字节是 8 位二进制,拆成两半正好是两位十六进制。任何二进制序列,你用十六进制压缩展示,长度只有原来的四分之一,而且每一个十六进制位都能用上面那张表秒转回对应的四位二进制,不会丢失信息。

举个直观例子,0b10110010这个字节,它等于十六进制0xB2。如果把B2展开,就是1011 0010。你不需要借助任何计算器,看到B立刻想到1011,看到2立刻想到0010,拼起来就是原始字节。这个“本能反应”一旦练成,读内存调试信息的速度能快一个量级。

嵌入式领域里面经常提到的“用 Verilog 设计十六进制键盘电路的键盘扫描和编码器”,本质上也是这个映射关系的应用:键盘矩阵里按下的键位置,被编码成 4 位二进制键值,再用一位十六进制0-F直接输出显示。硬件调试人员一眼就能看出按下的是几号键,比看 4 位二进制直观得多。

3.2 小数十六进制转换实操

小数部分转十六进制,核心步骤是:先把十进制小数转成二进制,然后从小数点开始向右每四位一组,最后一组不足四位时在右侧补零。

比如0.101:

  • 把101看成1010,因为小数点右侧要从左到右补够四位,所以是1010;
  • 查表1010是十六进制的A;
  • 结果是0x0.A。

再看一个复杂一点的,0.625的二进制是0.101,所以它的十六进制就是0x0.A。验证一下:十六进制A对应的四位二进制1010的权重是1/2 + 0/4 + 1/8 + 0/16 = 0.625,正确。

我再演示一个四组以上,带循环的例子。十进制0.1的二进制是0.00011001100110011...,从右往左每四位一组:

  • 0001 1001 1001 1001 1001...;
  • 查表就是1 9 9 9 9...。

所以0.1在十六进制下的近似表示是0x0.1999...。这个写法在 Python 里可以直接验证,后面工具章节会详细演示。

3.3 综合示例:12.375 的二进制与十六进制写法

把整数和小数合到一起走一遍完整流程。十进制12.375:

  • 整数12除2取余:1100;
  • 小数0.375乘2取整:0.375×2=0.75取0;0.75×2=1.5取1,剩0.5;0.5×2=1.0取1。所以小数部分是011;
  • 合起来:1100.011。

转十六进制:

  • 整数部分1100不用拆,正好四位,查表是C;
  • 小数部分011不够四位,右侧补零成0110,查表是6;
  • 最终结果是0xC.6。

再来验证:十六进制C.6展开为二进制是1100.0110,去掉末尾补零的0,正好是1100.011,和前面一致。这道综合题值得你亲手算两遍,因为它是整数除2取余、小数乘2取整、以及补零分组三个知识点的一次完整串联。

4. 负数的小数表示:补码与定点数

热词里面总有人搜“负数的二进制”,说明这也是新手绕不过去的坎。负数在计算机里的二进制表示不是简单地在前面加个负号,它遵循“补码”规则。

4.1 补码并不是“密码”,而是模运算的计数器

补码的核心思想可以类比一个里程表。假设一个 4 位里程表,从0000开始加 1,顺序变成0001、0010……一直到1111,再走一步就滚回0000。这就是“模16”的运算。在这种表盘上,-1如果用1111表示,那么5 + (-1)就相当于0101 + 1111 = 1 0100,溢出的最高位不要,低位是0100,正好是 4。这比设计“原码 + 符号位”的加法电路简单得多。

所以补码的生成规则“取反加一”不是天降神律,而是为了满足x + (-x) = 0这个等式。拿 4 位二进制举例:

  • 5的二进制是0101;
  • 取反得1010;
  • 加一得1011;
  • 所以-5的补码是1011。
  • 验证:0101 + 1011 = 10000,丢弃最高位进位,结果是0000,成立。

4.2 定点小数的负数怎么表示

定点小数是早期嵌入式系统和数字信号处理里常用的格式,它的思路是把小数点固定在某一个二进制位后面。比如 Q3.4 格式,意思就是 3 位整数位、4 位小数位,符号位单独占最高位。

在这种格式里,0.625的定点表示是0.1010(保留4位小数)。那-0.625怎么表示?先把0.1010当成一个整体数值,取反加一:

  • 原码(符号位+数值位)0.1010;
  • 取反得1.0101;
  • 加一得1.0110。

所以-0.625的 Q1.4 补码是1.0110。这里你看到的1.0110的“1”是符号位,也代表负数,但它还参与模运算,不是简单把原码的 0 换成 1 就行的。这也是新手最容易误解的地方:补码的符号位不是“开关”,而是数轴的一部分。

4.3 C语言float的十六进制真相:符号位、指数位、尾数位

现代 C 语言里的 float 用的是 IEEE 754 标准,和上面的定点数不太一样,它是“浮点”的,意思是小数点的位置可以浮动,靠指数位控制。一个 32 位 float 的结构是:

  • 最高 1 位:符号位;
  • 中间 8 位:指数位(带偏移量);
  • 低 23 位:尾数位(存储二进制小数)。

十进制0.75转成二进制是0.11,科学计数法写成1.1 × 2^(-1)。套进 float 结构,符号位 0,指数位存-1 + 127 = 126即二进制01111110,尾数位存1.1小数点后面的10000000000000000000000。整个拼起来是00111111010000000000000000000000,转十六进制就是0x3F400000。

这就是为什么你在调试器里看到 float 的十六进制长成0x3F400000这样的“大数字”——它其实打包了三个独立字段。你理解了打包规则,拿笔就能把十六进制反推回十进制。现在网上不少人问“float 的十六进制怎么看”,大部分答案只给转换工具,我给的建议是亲手拆一次字段,比用一万次工具都管用。

5. 用工具验证转换结果:命令、脚本与十六进制编辑器

手算容易错,所以实际做进制转换时我会用工具交叉验证。工具链搭起来很简单,几行命令就能覆盖你 90% 的需求。

5.1 Linux命令行里快速做进制转换

Linux 下最常用的两个工具是printf和bc。printf能快速输出整数格式:

# 十进制255转十六进制 printf '%x\n' 255 # 十进制255转八进制 printf '%o\n' 255 # 十六进制FF转十进制(需要借助算术展开) echo $((0xFF))

bc则可以做小数级的进制转换,配合ibase和obase使用:

# 十进制小数0.625转二进制,精度设为10位 echo 'obase=2; 0.625' | bc -l # 十进制0.1转二进制,你马上能看到循环小数 echo 'obase=2; scale=20; 0.1' | bc -l

实测下来,bc在处理小数的二进制输出时能直接展示无限循环的趋势,比如0.1会输出一串0.00011001100110011001100,对理解“算不干净”特别直观。不过要注意bc的小数进制转换在不同发行版上行为有细微差异,遇到输出异常时先检查scale设置。

5.2 Python的float.hex():一把钥匙打开全部秘密

如果你手头装了 Python,那验证小数进制转换的最佳工具是float.hex()方法。它能把一个浮点数的真实二进制内容,用十六进制直接打印出来:

# 查看0.1在双精度下的真实存储 print((0.1).hex()) # 输出:0x1.999999999999ap-4

这个输出怎么读?0x1.999999999999ap-4的意思是1.999999999999a(十六进制)× 2^(-4)。展开0x1.999999999999a的尾数部分,其实就是0.1的二进制循环小数截断到 52 位后再转十六进制的样子。我强烈建议你把几个常用小数都跑一遍,用这个命令反向验证你手算的结果,对理解浮点存储帮助极大。

再看一个0.75:

print((0.75).hex()) # 输出:0x1.8000000000000p-1

0x1.8就是二进制1.1,指数p-1代表乘以 2 的 -1 次方,最终就是0.75。整个表达式的可读性比直接看一长串二进制强太多。

5.3 用HxD观察二进制文件:为什么十六进制比十进制更顺眼

热词里有“十六进制编辑器 hxd”,这是 Windows 平台上一款非常经典的十六进制编辑器。打开一个二进制文件,比如图片、可执行文件,你看到的是一排排从00到FF的字节值。比如文件头里常见的89 50 4E 47,就是 PNG 图片的魔数。

为什么这里不用十进制显示?因为一个字节包含 8 位,用十六进制恰好映射成 2 位,等于一个字节固定两列,宽度完全对齐,扫一眼就知道文件结构。如果换成十进制显示,一个字节可能是 1 到 3 位数字,列宽根本无法对齐,人脑也没法快速把它拆回二进制位。十六进制的“友好”在这里体现得淋漓尽致。

另外 HxD 还有一个我常用的功能:内存对比。把内存中的原始字节直接导出来和文件字节对比,确认某个浮点数在内存中的实际字节序是大端还是小端。Windows 上小端序居多,0x3F400000在内存里实际存储为00 00 40 3F,这个顺序颠倒问题,不亲眼看一次很容易在底层调试时栽跟头。

6. 常见问题与避坑速查(附独家排查心得)

最后汇总一些我实际踩过、以及被问过多次的问题,很多人搜热词就是想解决这类具体疑问,这里直接给结论。

6.1 0.1 + 0.2 不等于0.3 的完整推导

很多人只知道“浮点数有误差”,但不知道误差怎么来的。0.1转二进制是无限循环小数0.0001100110011...,0.2同样在二进制下无限循环。由于 double 只能存 52 位尾数,这两个数都被截断,相加结果也不是精确的0.3。

具体过程可以用 Python 验证:

print(0.1 + 0.2) # 0.30000000000000004 print((0.1 + 0.2) == 0.3) # False

解决方式一般有三种:比较时用误差范围,比如abs(a - b) < 1e-9;或者做固定小数位运算时全部乘成整数;或者直接用decimal.Decimal:

from decimal import Decimal Decimal('0.1') + Decimal('0.2') # Decimal('0.3')

核心原则是:知道你在用二进制近似表示小数,就不要用“==”去比较浮点数。

6.2 补零方向搞反,结果全错的陷阱

这是我在教别人十六进制转换时遇到最多的错。整数部分转十六进制,从右往左四位一组,最后一组不足四位要在左侧补零;小数部分转十六进制,从左往右四位一组,最后一组不足四位要在右侧补零。

我自己记的方法是:把小数点当成“锚点”,哪个方向离锚点远,补零就补在哪边。整数部分的小数点在最右侧,离最左边远,所以补在最左;小数部分的小数点在最左侧,离最右边远,所以补在最右。这个“远离锚点补零”的心法,我还没在任何教科书上见过,但确实帮我少错了一百次。

6.3 strstr()查二进制内存为什么翻车

C语言里strstr()是字符串查找函数,它按\0判断结尾。但二进制数据里完全可以包含0x00字节,比如浮点数0.75的 IEEE 754 表示0x3F400000内存里就是00 00 40 3F,前面就有两个0x00。你用strstr()去查,它会在第一个\0处就停止,怎么可能找到后面的数据。

正确做法是用memmem()(GNU扩展)或者自己写一个按长度比较的循环:

// 用memmem按长度在二进制缓冲区中查找模式 void *pos = memmem(buf, buf_len, pattern, pattern_len);

核心教训:凡是面向字节的二进制处理,一律带长度,不要依赖任何“以\0结尾”的字符串函数。这个坑在解析文件格式、通信报文时特别常见。

6.4 从键盘扫描到CTF:十六进制在嵌入式与安全的用处

热词里有“用Verilog设计一个十六进制键盘电路的键盘扫描和编码器”,也有“CTF二进制中的rip题目”,两者看起来不相关,其实都用到了十六进制的“字段打包”思维。

键盘扫描电路把 4 行 4 列的按键矩阵编码成 4 位二进制,四位的组合正好一位十六进制。硬件输出直接接数码管显示0到F,调试的时候你不用盯着四位线折腾,看一个字符就知道哪个键被按下。

CTF 的 pwn 题目里改 return address(也就是热词提到的“rip”)时,目标地址通常是一串十六进制,比如0x7fffffffdbe0。你在构造 payload 时还得考虑字节序:小端机器上地址要按字节倒序写入内存。只给一个十六进制串而不讲字节序的教程,基本都会让你在最后一步翻车。我建议遇到这类题,先用 Python 的p32()或struct.pack('<Q', addr)处理地址,再放进 exploit 脚本,不要手动去比对内存顺序。

这些实际案例都指向同一个道理:二进制是一切机器语言的底层语言,而十六进制是人与二进制之间的桥梁。你把小数的二进制、十六进制转换吃透了,再去理解浮点、内存布局、网络字节序,都是顺水推舟的事。

最后分享一点个人体会

我最早学进制转换的时候,纯靠背步骤,整数除2取余、小数乘2取整、四位一分组,背得滚瓜烂熟,但一遇到0.1转十六进制这种小数就发懵。后来我突然想明白,进制转换的关键不是步骤,而是“权重”这两个字。二进制的每一位就是一个二进制的秤砣,十六进制的每一位就是四个二进制的秤砣捆在一起。一旦你把转换看成“用秤砣配重”而不是“套公式”,所有步骤都变得特别自然。

如果你也想练出这种直觉,我建议你拿一张纸,写上几个带小数的十进制数,比如6.75、10.25、0.3125,先用手算转二进制,再转十六进制,最后用文章里的bc或者 Python 的float.hex()验证。误差不匹配就回去检查补零方向和舍入方式。练上三五天,你再看内存调试页面里那些十六进制字节,就不再是乱码,而是能一眼读出它背后真实数值。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 5:13:00

AI工作台搭建指南:Skill组合与工作流编排实战

1. 从单点工具到组合拳&#xff1a;为什么你需要一个AI工作台很多人用AI的方式还停留在“打开一个对话框&#xff0c;问一个问题&#xff0c;复制答案&#xff0c;关掉”的阶段。这种用法不是不行&#xff0c;但效率天花板极低。你每次都在重新交代背景、重新设定角色、重新调整…

作者头像 李华
网站建设 2026/10/2 5:12:52

多波束测深数据处理全流程解析:从原始文件到高精度海底地形

简介&#xff1a;本资源是一篇聚焦海洋测绘前沿技术的综述性学术论文&#xff0c;面向测绘工程、海洋科学、水下探测等领域的科研人员、高校师生及工程技术人员&#xff0c;系统梳理多波束测深数据处理的关键瓶颈与突破路径。全文围绕声线跟踪、误差校正、数据融合、几何校正、…

作者头像 李华
网站建设 2026/10/2 5:12:52

Agent Memory 记忆系统实战:从 LLM 到 MCP 与 Docker 部署

1. 从 "hindsight" 这个名字说起&#xff1a;为什么 Agent Memory 值得单独做一个项目第一次看到 "hindsight" 这个词&#xff0c;我脑子里蹦出来的不是词典释义&#xff0c;而是那种"事后复盘"的直觉——事情已经发生了&#xff0c;回头看&…

作者头像 李华
网站建设 2026/10/2 5:12:08

AI智能体+Office套件:从架构设计到文档自动化实现全解析

在计算机科学与技术的毕业设计里&#xff0c;AI智能体是最不缺热度、也最不缺同质化的方向。但"AI智能体Office套件"这个组合&#xff0c;恰好把虚的智能体落到实的办公场景上——既沾了大模型和Agent的热点&#xff0c;又有完整的工程链路可以展开&#xff0c;从模型…

作者头像 李华
网站建设 2026/10/2 5:12:08

激光雷达气溶胶数据处理:从原始回波到可信廓线的关键一跃

简介&#xff1a;这份PDF文献聚焦激光雷达探测大气气溶胶的数据处理研究&#xff0c;面向大气科学、环境监测及遥感方向的学习者与科研人员&#xff0c;帮助理解米散射激光雷达的系统构成与反演算法原理。资源包内含1个PDF文件&#xff0c;大小约180KB&#xff0c;内容源自期刊…

作者头像 李华
网站建设 2026/10/2 5:12:08

终端滑模控制详解:从有限时间收敛到非奇异设计

1. 从“收敛速度不够快”说起&#xff1a;终端滑模到底做了什么先聊个场景。你在做机械臂轨迹跟踪&#xff0c;用了经典的滑模控制&#xff0c;仿真波形也漂亮&#xff0c;误差曲线也确实收敛到零了。可审稿人或者老板一句话问过来&#xff1a;“你这个收敛速度能不能更快&…

作者头像 李华