IEEE 754浮点标准:单精度与双精度格式、规格化与非规格化数、特殊值、舍入模式
好,咱们正式开始聊浮点数的底层标准。
做FPGA这么久,我见过太多同学在浮点运算上栽跟头。说白了,很多人以为浮点数就是“带小数点的数”,结果一上硬件,精度对不上、特殊值没处理、舍入方式搞错……板子调三天,最后发现是标准没吃透。
所以这一章,咱们把IEEE 754这个“浮点世界的基本法”彻底捋一遍。你想想看,没有这个标准,不同厂商的芯片算出来的结果都不一样,那还怎么玩?
2.1 单精度与双精度格式
IEEE 754定义了两种最常用的格式:单精度(32位)和双精度(64位)。
我个人习惯把它们的结构记成三部分:符号位、指数位、尾数位。就这么简单。
| 格式 | 总位数 | 符号位 | 指数位 | 尾数位 | 指数偏移量 |
|---|---|---|---|---|---|
| 单精度 | 32 | 1 | 8 | 23 | 127 |
| 双精度 | 64 | 1 | 11 | 52 | 1023 |
嗯,这里要注意:指数是带偏移量的。什么意思?单精度的指数实际值 = 存储值 - 127。比如存储的指数是130,那实际指数就是3。这个设计是为了能表示正负指数,又不用补码,挺巧妙的。
核心公式:
数值 = (-1)^符号 × 2^(指数-偏移量) × 1.尾数
注意那个“1.”——规格化数默认隐藏了一个整数位的1。
2.2 规格化与非规格化数
这里有个坑,我当年刚做RISC-V浮点单元时就踩过。
规格化数:指数不全为0,也不全为1。尾数前面隐含一个“1.”。这是最常见的浮点数形式。
非规格化数:指数全为0。这时候隐含的整数位变成“0.”。为什么要搞这个?
说白了,就是为了表示非常接近0的数。如果只有规格化数,那最小的正数就是2^(-126)左右。但有了非规格化数,我们可以一直表示到2^(-149)附近。这叫“逐渐下溢”,而不是突然变成0。
我的经验:
在FPGA上实现非规格化数处理时,很多人直接把它当作0扔掉。我曾经在一个雷达信号处理项目里这么干过,结果信噪比差了3个dB。后来发现,那些微小的非规格化数恰恰是弱信号的关键信息。所以,除非你的应用对精度完全无所谓,否则别偷懒。
2.3 特殊值:NaN与Infinity
浮点数里有两个“特殊居民”:无穷大和NaN。
- Infinity(无穷大):指数全为1,尾数全为0。有正负之分。
- NaN(Not a Number):指数全为1,尾数不全为0。
NaN又分两种:
- Quiet NaN(安静NaN):最高尾数位为1。运算遇到它,不触发异常,继续传播。
- Signaling NaN(信令NaN):最高尾数位为0。运算遇到它,会触发无效操作异常。
避坑指南:
我曾经在实现RISC-V的FADD指令时,忘了处理NaN的传播规则。结果两个NaN相加,输出了一个乱七八糟的数。后来查了三天,才发现是尾数位的“NaN载荷”没有正确传递。记住:NaN的尾数部分可以携带诊断信息,不要随便清零。
2.4 舍入模式介绍
浮点运算的结果往往不能精确表示,这时候就需要舍入。IEEE 754定义了四种基本舍入模式:
| 舍入模式 | 说明 | 典型场景 |
|---|---|---|
| 就近舍入(偶) | 舍入到最近的可表示值,平局时取偶数 | 默认模式,大多数计算用这个 |
| 向+∞舍入 | 结果向上取整 | 区间运算的上界 |
| 向-∞舍入 | 结果向下取整 | 区间运算的下界 |
| 向0舍入 | 直接截断小数部分 | 整数转换、定点数处理 |
你可能会问:“就近舍入为什么还要分平局?”
举个例子:1.5要舍入到整数。离1和2一样近,怎么办?IEEE 754说取偶数,所以1.5 -> 2。2.5呢?也是取偶数,2.5 -> 2。这样做的目的是让统计误差不偏向任何一方。
FPGA实现要点:
在硬件里实现舍入,核心是处理三个位:
- 保留位(Guard bit):结果的最低有效位之后的一位
- 舍入位(Round bit):保留位之后的一位
- 粘滞位(Sticky bit):舍入位之后所有位的逻辑或
这三个位决定了最终怎么舍入。我建议你在写Verilog时,把这三位单独拉出来做状态机,别混在加法器里,否则调试起来很痛苦。
2.5 知识体系总览
下面这张图是我自己梳理的浮点标准核心脉络,画成SVG方便你对照着看。
2.6 实战中的一点感悟
说实话,IEEE 754标准看起来枯燥,但它是浮点运算的基石。我在做RISC-V浮点单元时,光是舍入逻辑就重构了三版。第一版直接用截断,结果数值误差累积到不可接受;第二版做了就近舍入,但没处理平局情况;第三版才老老实实按标准来。
所以我的建议是:别跟标准对着干。IEEE 754是无数前辈用血泪教训总结出来的,你照着做,至少不会出大错。
一个小技巧:
在FPGA上验证浮点单元时,我习惯用Python的struct模块把浮点数拆成二进制,然后跟RTL仿真结果逐位对比。这样能快速定位是符号位、指数还是尾数出了问题。
公众号:蓝海资料掘金营,微信deep3321