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方便你对照着看。

IEEE 754 浮点标准核心知识体系 IEEE 754 浮点标准 单精度与双精度格式 规格化与非规格化 特殊值 NaN/Inf 四种舍入模式 32位 vs 64位 指数偏移量 隐藏位 1.xxx 逐渐下溢 0.xxx QNaN vs SNaN 正负无穷大 Guard/Round/Sticky 就近/向上/向下/截断 核心:格式决定精度,特殊值处理边界,舍入控制误差 FPGA实现时,这三块缺一不可

2.6 实战中的一点感悟

说实话,IEEE 754标准看起来枯燥,但它是浮点运算的基石。我在做RISC-V浮点单元时,光是舍入逻辑就重构了三版。第一版直接用截断,结果数值误差累积到不可接受;第二版做了就近舍入,但没处理平局情况;第三版才老老实实按标准来。

所以我的建议是:别跟标准对着干。IEEE 754是无数前辈用血泪教训总结出来的,你照着做,至少不会出大错。

一个小技巧:

在FPGA上验证浮点单元时,我习惯用Python的struct模块把浮点数拆成二进制,然后跟RTL仿真结果逐位对比。这样能快速定位是符号位、指数还是尾数出了问题。


公众号:蓝海资料掘金营,微信deep3321