← 返回博客数制

IEEE 754 浮点数:从比特到数值

解码 IEEE 754 binary32 与 binary64:符号、偏置指数、尾随有效数字段、特殊值、舍入,以及非结合算术。

DennyDenny
阅读约需 9 分钟
Chalkboard feature illustration for “IEEE 754 浮点数:从比特到数值”

**要点速览:**对于普通二进制数值,IEEE 754 使用 (−1)s(1+f)2e−bias(-1)^s(1+f)2^{e-bias}:一个符号位、偏置指数,以及带隐含前导 1 的尾随有效数字段。保留的指数模式编码带符号零、次正规数、无穷大与 NaN。由于大多数精确实数结果都必须舍入到有限格式,浮点加法与乘法通常不满足结合律。

当范围与缩放比例已知时,定点二进制很好用;当一次计算必须覆盖相差许多个数量级的数值时,它就会变得不方便。例如,带 16 个小数位的有符号 32 位定点数,步长为 2−162^{-16},范围大约是 −32,768-32{,}768 到 32,767.9999832{,}767.99998。

浮点表示把部分比特分配给指数,让基数点随数值大小移动。IEEE 754 于 1985 年首次发布,当前有效修订版为 IEEE 754-2019。标准定义了二进制与十进制格式、运算、舍入、异常及交换行为。实现可以采用硬件、软件或两者结合,因此符合标准并不表示每颗 CPU 都在硅片中实现每一种格式。

为什么需要浮点数?

科学记数法在纸上解决同一问题:6.674×10−116.674 \times 10^{-11} 把较小的有效数字与较大的指数分开。IEEE 754 就是具有严格规则的二进制科学记数法,规定每个部分获得多少比特,以及如何编码。

格式由两项设计压力塑造:

  1. **规定的编码与运算。**数学数值与位模式并非一一对应:零有两个符号,NaN 则有许多载荷编码。标准规定运算与比较如何处理它们。
  2. **可实现的算术。**指数比较与有效数字对齐必须映射到有限的硬件或软件算法,并具有明确的舍入与异常行为。

为同时满足两者而作出的权衡——偏置指数、隐含前导位、特殊用途指数码——赋予 IEEE 754 独特的形状。

单精度布局(32 位)

| 31 | 30 ............ 23 | 22 ........................... 0 |
|  s |       e (8 bits)   |          m (23 bits)             |
| 1  |                    |                                  |
  • 符号位 ss:1 位。0 表示非负,1 表示负。
  • 指数 ee:8 位,以无符号整数形式存储,偏置为 127。
  • 尾随有效数字段 mm:23 位。对于正规数,它存储隐含前导 1 之后的小数部分。

对于规格化数(最常见情况),数值为:

v=(−1)s⋅(1+m223)⋅2e−127v = (-1)^s \cdot \left(1 + \frac{m}{2^{23}}\right) \cdot 2^{e - 127}

有效数字中的“1.1.”是隐含的——每一个规格化二进制数都能通过选择指数写成前导位为 1 的形式,因此格式可以省下一位。

双精度布局(64 位)

结构相同,但字段更宽:

| 63 | 62 ........... 52 | 51 ............................. 0 |
|  s |     e (11 bits)   |          m (52 bits)              |
  • 符号位 ss:1 位。
  • 指数 ee:11 位,偏置为 1023。
  • 尾随有效数字段 mm:52 位。

数值(规格化):

v=(−1)s⋅(1+m252)⋅2e−1023v = (-1)^s \cdot \left(1 + \frac{m}{2^{52}}\right) \cdot 2^{e - 1023}

范围约为 ±1.8×10308\pm 1.8 \times 10^{308},并具有约 15–17 位有效十进制数字。

为什么指数要加偏置?

把指数存为 e−biase - bias 而不是二进制补码,有一项微妙但重要的好处:浮点比较可以归约为对位模式进行整数比较(并翻转符号位)。如果把两个非负浮点数视作 32 位无符号整数,较大的浮点数也对应较大的整数。二进制补码指数会在正负指数边界上破坏这种单调关系。

全零指数段保留给零与次正规数,全 1 指数段保留给无穷大与 NaN。对于 binary32,普通指数段 1 到 254 对应无偏指数 −126-126 到 +127+127。

特殊情况

指数位尾随有效数字位含义
全零全零±0\pm 0(由符号位决定)
全零非零次正规数
中间任意值任意值规格化数
全 1全零±∞\pm \infty
全 1非零NaN(非数)

零有两种编码(+0+0 与 −0-0)。在 IEEE 比较中两者相等,但某些运算会产生不同的带符号结果,例如 1/+0=+∞1 / +0 = +\infty、1/−0=−∞1 / -0 = -\infty。

次正规数(历史上也称非规格化数)填补零与最小正规数之间的空隙。隐含前导 1 变为 0,binary32 使用有效指数 −126-126。这实现渐进下溢:次正规范围内的绝对间距保持恒定,而相对精度在接近零时逐渐下降。

无穷大来自溢出或除以零。它按直观方式在算术中传播:∞+1=∞\infty + 1 = \infty,∞×0=NaN\infty \times 0 = \text{NaN}。

NaN表示无效运算:−1\sqrt{-1}、0/00 / 0、∞−∞\infty - \infty。NaN 具有传染性——任何包含 NaN 的算术都得到 NaN。关键是,在 IEEE 754 比较中 NaN≠NaN\text{NaN} \neq \text{NaN},所以测试套件使用 isnan(),而不是 ==。

解码示例:0x40490FDB

这是一个著名的 32 位常数。写成二进制:

0x40490FDB = 0100 0000 0100 1001 0000 1111 1101 1011

分割字段:

  • 符号:0——正数
  • 指数:1000 0000 = 12810128_{10}——实际指数 128−127=1128 - 127 = 1
  • 尾随有效数字:100 1001 0000 1111 1101 1011

有效数字为 1.(trailing bits)21.\text{(trailing bits)}_2,即:

1.1001001000011111101101121.10010010000111111011011_2

计算存储的小数部分:

mfrac=0b10010010000111111011011223=4,788,1878,388,608≈0.5707963m_{frac} = \frac{0b10010010000111111011011}{2^{23}} = \frac{4{,}788{,}187}{8{,}388{,}608} \approx 0.5707963

因此有效数字为 1.57079631.5707963,乘以 212^1:

v≈3.1415927v \approx 3.1415927

这就是单精度下的 π\pi。常数 0x40490FDB 是 π\pi 的经典 IEEE 754 单精度编码,广泛出现在数值库、GPU 着色器与世界各地的考试题中。

编码示例:5.75

把 5.755.75 转换为单精度 IEEE 754。

第 1 步:转换为二进制。5=10125 = 101_2。0.75=0.5+0.25=0.1120.75 = 0.5 + 0.25 = 0.11_2。合在一起:5.75=101.1125.75 = 101.11_2。

**第 2 步:规格化。**把二进制点向左移动,直到左侧恰好只剩一位:

101.112=1.01112×22101.11_2 = 1.0111_2 \times 2^2

因此实际指数为 22。

第 3 步:编码各字段。

  • 符号:正数,所以 s=0s = 0。
  • 指数:实际值 22 加偏置 127127 得 129129,也就是 1000 0001。
  • 尾随有效数字:删除前导 1,存储接下来的 23 位:0111 后跟 19 个零,即 01110000 00000000 0000000。

第 4 步:组装。

0 | 10000001 | 01110000000000000000000

十六进制为 0x40B80000。反向执行解码过程,可以验证该模式准确恢复 5.75。

注意,5.75 具有精确二进制表示——其小数部分是二的负幂之和(2−1+2−22^{-1} + 2^{-2})。大多数十进制小数并非如此。单精度下,0.10.1 是 0x3DCCCCCD,解码回来约为 0.1000000014901...0.1000000014901...,误差约为 10−910^{-9}。这个微小误差就是初学者每次遇到“0.1+0.2≠0.30.1 + 0.2 \neq 0.3”惊讶的根源。

舍入方向

IEEE 754-2019 规定五种舍入方向属性:

  • 舍入到最近值,中间值取偶数(默认)。选择较接近的可表示值;恰好位于中间时,选择有效数字最低位为 0 的值。
  • **舍入到最近值,中间值远离零。**该属性尤其与十进制算术相关,并非每个二进制浮点环境都会暴露它。
  • 向零舍入(截断)。
  • 向 +∞+\infty 舍入(上取整)。
  • 向 −∞-\infty 舍入(下取整)。

默认模式可以减小统计偏差——直接“逢半向上”会在累积大量数值时引入轻微向上偏差,而逢半取偶能够将其平均掉。

应用能够选择哪些方向,取决于语言、运行时、编译器与硬件环境。可复现性还取决于求值顺序、中间精度、融合运算、库函数与次正规数处理,而不仅是选定的舍入方向。

浮点算术为什么不满足结合律

考虑三个 IEEE 754 单精度值:a=1020,b=−1020,c=1a = 10^{20}, b = -10^{20}, c = 1。

(a+b)+c(a + b) + c:先算 a+b=0a + b = 0,结果精确(抵消干净);再算 0+c=10 + c = 1。最终结果为 11。

a+(b+c)a + (b + c):先算 b+cb + c。bb 的指数约为 67,cc 的指数为 0。对齐有效数字会把 cc 移到远超 binary32 精度的位置。舍入后 b+c=bb+c=b,随后 a+b=0a+b=0。

两种加括号方式产生不同答案,因此浮点加法不满足结合律。编译器是否可以重新结合表达式,取决于语言规则、实现、优化标志与浮点环境。对可复现性敏感的代码应记录这些选择并测试实际工具链,而不是把某一个编译器标志当成通用保证。

同样的性质也适用于减法(抵消情况)、乘法(极小乘积可能下溢到零)与除法(除以极小数可能溢出为无穷大)。

硬件:FPU

硬件浮点加法器通常执行如下阶段;没有 FPU 的处理器可以用软件实现相同语义:

  1. 解码两个操作数的字段。
  2. 比较指数;把指数较小的有效数字按差值右移,并保留保护位、舍入位与粘滞位用于舍入。
  3. 对齐后执行有效数字加减(使用宽超前进位风格加法器)。
  4. 左移结果直到出现前导 1,完成规格化,并相应调整指数。
  5. 按当前舍入模式舍入。
  6. 把结果编码为 IEEE 754 格式。

数据通路需要指数比较、有效数字对齐、加/减逻辑、前导零检测、规格化、舍入、特殊情况处理与异常报告。RISC-V 的 F 扩展定义 binary32 指令,D 则增加 binary64;两者都是可选扩展,说明标准格式与强制硬件之间存在区别。

有效数字路径会复用二进制算术技术,但 FPU 不只是加了额外管线的整数 ALU:舍入、渐进下溢、NaN、无穷大、带符号零与异常都是其体系结构行为的一部分。

更宽格式

除单精度与双精度外,IEEE 754 还定义:

  • 半精度(binary16):1 位符号 + 5 位指数 + 10 位尾随有效数字。用于图形与降低精度的数值工作负载。
  • 四倍精度(128 位):1 + 15 + 112。较少使用,主要用于数值分析与某些金融计算。
  • bfloat16(不是 IEEE 754 基本交换格式):1 + 8 + 7。它保留类似 binary32 的指数范围,但精度低得多,广泛用于机器学习。

每种变体都针对目标工作负载,在范围与精度之间作出权衡。

常见陷阱

  • **没有错误模型就使用 ==。**对某些不变量与离散值,精确比较完全正确;近似结果则需要领域相关的绝对/相对容差或末位单位策略。
  • **忘记 NaN != NaN。**使用 isnan() 检测 NaN。
  • **假定结合律成立。**重新排列求和会改变答案。编译器标志可能允许不安全的重新结合,使用前必须理解其含义。
  • 混淆十进制与二进制直觉。0.10.1 没有有限的精确二进制表示。对于十进制业务规则,可考虑缩放整数、十进制浮点或 BCD,并仍需定义每一个必要边界上的舍入。
  • **假定次正规数性能。**有些处理器可以全速处理次正规数,另一些会有性能损失或支持刷新为零模式。应测量目标系统,并在正确性需要时保留渐进下溢。

连接到更广的 CPU 架构

寄存器组织取决于 ISA。有些架构使用分离的整数与浮点寄存器文件,另一些则让部分状态重叠或统一。加载、存储、转换、移动与调用约定共同定义边界。

对于更宽的算术,整数侧依赖与 8 位加法器相关的结构,并按目标规模与优化;浮点侧则加入对齐、规格化、舍入、异常与特殊值处理。

下一步

继续阅读二进制补码详解,理解定宽有符号整数;随后阅读 BCD:二进制编码十进制基础,理解面向数字位的十进制编码。

要在位级进行实验,可使用 ALU 风格的数据通路完成有效数字算术,再把对齐、规格化、保护/舍入/粘滞位与特殊情况分别建模为独立阶段。

规范性参考是当前有效的 IEEE 754-2019 浮点算术标准。

将想法变成真正可运行的电路。