浮点数和 IEEE 754

2262 字
11 分钟
浮点数和 IEEE 754

浮点数和 IEEE 754#

浮点数的表示格式#

为什么需要浮点数?#

定点数的小数点位置固定,表示范围非常有限。比如 32 位定点数,若 16 位整数 + 16 位小数,最大只能表示约 65536。

浮点数的思路:模仿科学计数法,让小数点可以”浮动”,用有限的位数表示极大的动态范围。

十进制科学计数法:3.14×108\text{十进制科学计数法:} \quad 3.14 \times 10^8二进制浮点数:(1)s×M×2E\text{二进制浮点数:} \quad (-1)^{s} \times M \times 2^{E}

三个核心字段#

字段含义作用
符号 s(sign)0 表示正,1 表示负决定正负
尾数 M(mantissa / significand)有效数字部分决定精度
阶码 E(exponent)指数,用 移码 存储决定范围

阶码使用移码而非补码的原因,见 数据表示#移码。核心原因:移码让硬件用无符号比较器就能正确比较浮点数大小。

真值公式#

通用形式:

N=(1)s×M×rEN = (-1)^{s} \times M \times r^{E}

其中 rr 是基数(二进制下 r=2r = 2)。尾数 MM 通常是纯小数或带一位整数。


浮点数的规格化#

什么是规格化?#

当一个浮点数有多种等价表示时,硬件处理会很麻烦。例如十进制下:

0.12×102=1.2×101=12×100=120×1010.12 \times 10^2 = 1.2 \times 10^1 = 12 \times 10^0 = 120 \times 10^{-1}

规格化就是规定统一形式:让尾数的最高位是有效数字。

对于二进制,规格化要求尾数的最高位为 1(即 1M<21 \leq M < 2):

1.01101×250.101101×26×(非规格化)1.01101 \times 2^5 \quad \checkmark \qquad 0.101101 \times 2^6 \quad \times \quad\text{(非规格化)}

隐藏位(Hidden Bit)#

既然规格化后尾数的整数部分永远是 1,那就不需要存储它。IEEE 754 用这个”隐藏位”白嫖一位精度。

23 位存储 + 1 位隐藏 = 24 位有效精度(单精度) 52 位存储 + 1 位隐藏 = 53 位有效精度(双精度)

非规格化数(Denormalized Numbers)#

如果只允许规格化形式,最接近 0 的就是 1.0×21261.0 \times 2^{-126}(单精度),再小的数就会下溢到 0,造成精度断层。

IEEE 754 用阶码全 0 的特殊编码表示非规格化数,此时隐藏位变为 0:

M=0.fraction,E=126 (单精度)M = 0.\text{fraction}, \quad E = -126\ (\text{单精度})

这样就能平滑过渡到 0(gradual underflow)。


浮点数的加减运算#

浮点数加减不能直接对尾数做运算——阶码不同相当于”数量级”不同。需要经过以下步骤:

五步流程#

1. 对阶    2. 尾数加减    3. 规格化    4. 舍入    5. 溢出判断\begin{array}{c} \text{1. 对阶} \;\longrightarrow\; \text{2. 尾数加减} \;\longrightarrow\; \text{3. 规格化} \;\longrightarrow\; \text{4. 舍入} \;\longrightarrow\; \text{5. 溢出判断} \end{array}

第一步:对阶(Align Exponents)#

原则:小阶向大阶看齐。阶码小的那个数的尾数右移,阶码增大。

为什么小阶向大阶?若大阶向小阶看齐,尾数左移会丢失高位——那可是最显著的数字。

E=EAEBE_{\text{差}} = |E_A - E_B|

阶码小的尾数右移 EE_{\text{差}} 位,阶码同步增加到与另一个相同。

第二步:尾数加减#

对阶完成后,两个尾数直接做加减(此时阶码相同了)。

第三步:规格化#

运算结果可能不再满足规格化形式。两个极端情况:

  • 尾数溢出:和 2\geq 2(如 1.101+1.011=11.0001.101 + 1.011 = 11.000)→ 尾数右移 1 位,阶码 +1
  • 尾数过小:结果高位有多个 0(如 1.0011.000=0.0011.001 - 1.000 = 0.001)→ 尾数左移直到最高位为 1,阶码同步减小

第四步:舍入(Rounding)#

规格化后,尾数可能超出可存储的位数,需要舍入。详见 IEEE 754 舍入与精度

第五步:溢出判断#

  • 阶码超出最大表示范围 → 上溢(overflow),通常变为 ±\pm\infty
  • 阶码低于最小表示范围 → 下溢(underflow),变为 0 或非规格化数

完整示例#

计算 1.5+0.751.5 + 0.75(十进制)在单精度下的过程:

步骤A=1.5A = 1.5B=0.75B = 0.75
真值1.5=1.1×201.5 = 1.1 \times 2^00.75=1.1×210.75 = 1.1 \times 2^{-1}
符号/阶码/尾数s=0, E=0, M=1.1s=0, E=-1, M=1.1

1. 对阶:B 的阶码 -1 小于 A 的阶码 0,差 = 1。B 的尾数右移 1 位:

MB=1.1右移1位0.11,EB=0M_B = 1.1 \xrightarrow{\text{右移1位}} 0.11, \quad E_B = 0

2. 尾数加

MA+MB=1.1+0.11=10.01M_A + M_B = 1.1 + 0.11 = 10.01

3. 规格化:尾数 10.01210.01 \geq 2,右移 1 位:

M=1.001,E=0+1=1M = 1.001, \quad E = 0 + 1 = 1

4. 舍入1.0011.001 在 23 位精度内,无变化。

5. 结果(1)0×1.001×21=10.012=2.25(-1)^0 \times 1.001 \times 2^1 = 10.01_2 = 2.25


IEEE 754 格式#

IEEE 754 是浮点数的事实标准(1985 年制定,2008/2019 年修订),几乎所有的 CPU 和 GPU 都遵循。

两种常用精度#

精度总位数符号阶码尾数(存储)尾数(有效)偏置值
单精度 (float)32182324127
双精度 (double)6411152531023

偏置值通式:bias=2n11bias = 2^{n-1} - 1nn 为阶码位数)

阶码编码与特殊值#

阶码使用保留编码来区分不同情况:

阶码(移码)尾数含义
0000000\ldots 0= 0±0
0000000\ldots 0≠ 0非规格化数(denormalized)
0001000\ldots 1 ~ 1110111\ldots 0任意规格化数(normalized)
1111111\ldots 1= 0±∞(无穷大)
1111111\ldots 1≠ 0NaN(Not a Number)

特殊值详解#

零(±0)#

IEEE 754 区分 +0 和 -0。两者在比较时视为相等,但在除法中行为不同(1/+0=+1/+0 = +\infty1/0=1/-0 = -\infty)。

无穷大(±∞)#

上溢时产生。\infty 可以参与运算:

  • 3+=3 + \infty = \infty
  • 3/=03 / \infty = 0
  • =NaN\infty - \infty = \text{NaN}(未定义)

NaN(Not a Number)#

非法操作的结果:0/00/0\infty - \infty1\sqrt{-1}×0\infty \times 0

NaN 有两种:

  • Signaling NaN (sNaN):触发异常,用于调试
  • Quiet NaN (qNaN):静默传播,不触发异常

关键特性:NaNNaN\text{NaN} \neq \text{NaN},任何与 NaN 的比较都返回 false。检查 NaN 只能用 isnan()

数值范围#

精度最小正规格化数最大正数最小正非规格化数
单精度21261.18×10382^{-126} \approx 1.18 \times 10^{-38}3.40×1038\approx 3.40 \times 10^{38}21491.40×10452^{-149} \approx 1.40 \times 10^{-45}
双精度210222.23×103082^{-1022} \approx 2.23 \times 10^{-308}1.80×10308\approx 1.80 \times 10^{308}210744.94×103242^{-1074} \approx 4.94 \times 10^{-324}

IEEE 754 舍入与精度#

为什么需要舍入?#

浮点数的尾数位数有限。运算结果的有效位数如果超过存储容量,就必须舍入。IEEE 754 规定了四种舍入模式。

四种舍入模式#

模式行为应用
舍入到最近偶数 (roundTiesToEven)舍入到最近值;正好在中间时取最低位为 0 的值(偶数)默认模式,最常用
向 0 舍入 (roundTowardZero)直接截断,绝对值变小C 语言的类型转换
向 +∞ 舍入 (roundTowardPositive)总是向上取区间算术上界
向 -∞ 舍入 (roundTowardNegative)总是向下取区间算术下界

舍入到最近偶数为啥是默认?#

普通的”四舍五入”在大量运算中会有统计偏差——中间值总是向上舍,导致结果系统性偏大。而最近偶数模式在中间值上有一半情况向上、一半情况向下,无偏

需要舍入的值舍入到最近偶数
1.4 → 1✓(更近)
1.6 → 2✓(更近)
1.5 → 2偶数(1 是奇数,2 是偶数)
2.5 → 2偶数(3 是奇数,2 是偶数)

保护位、舍入位、粘贴位(Guard, Round, Sticky)#

硬件用额外的三个位来确保舍入正确:

含义
Guard (G)尾数最低位之后的第一位
Round (R)Guard 位之后的第二位
Sticky (S)Round 之后所有位的逻辑或(只要后面有任何 1,S 就是 1)

舍入决策:

  • 若 G=0 → 截断
  • 若 G=1 且 R=S=0 → 最近偶数(看最低位)
  • 若 G=1 且 (R=1 或 S=1) → 向上进位

C 语言中的浮点数类型#

类型映射#

C 类型IEEE 754 对应位数有效数字(十进制)
float单精度32~7 位
double双精度64~15-16 位
long double平台相关x86: 80; ARM: 128取决于实现

默认类型:C 中浮点字面量(如 3.14)默认为 double。赋给 float 时发生隐式转换。

float.h 关键宏#

#include <float.h>
// float
FLT_MAX // ≈ 3.402823e+38 最大正数
FLT_MIN // ≈ 1.175494e-38 最小正规格化数
FLT_EPSILON // ≈ 1.192093e-07 1.0 与下一个可表示数之差
FLT_DIG // 6 十进制有效位数
// double
DBL_MAX // ≈ 1.797693e+308
DBL_MIN // ≈ 2.225074e-308
DBL_EPSILON // ≈ 2.220446e-16
DBL_DIG // 15
// true minimum (denormalized)
FLT_TRUE_MIN // ≈ 1.401298e-45 最小正非规格化数
DBL_TRUE_MIN // ≈ 4.940656e-324

FLT_EPSILON 不是最小正数,而是1 和下一个可表示浮点数之间的距离——即浮点数的”分辨率”。

常见陷阱#

1. 不要用 == 比较浮点数#

float a = 0.1f + 0.2f; // 实际可能是 0.30000001
if (a == 0.3f) // 可能是 false!
printf("equal\n");
// ✅ 正确做法:用容差
if (fabs(a - 0.3f) < 1e-6)
printf("approximately equal\n");

2. 大数吃小数(Catastrophic Cancellation)#

float big = 1.0e8f;
float small = 1.0f;
float sum = big + small; // sum 仍然是 1.0e8f
// small 被"吞掉了",因为单精度只有约 7 位有效数字

当两个数数量级差超过有效位数时,较小的数对结果完全无贡献。

3. 结合律不成立#

浮点加减不满足结合律:

(a+b)+ca+(b+c)(a + b) + c \neq a + (b + c)
float a = 1.0e8f, b = -1.0e8f, c = 1.0f;
(a + b) + c // = 0 + 1 = 1 ✓
a + (b + c) // = 1.0e8 - 1.0e8 = 0 ✗

编译器不能随意重排浮点运算顺序(除非开启 -ffast-math)。

4. 浮点数到整数的截断#

int x = (int)3.14f; // x = 3(向 0 截断)
int y = (int)-3.14f; // y = -3(向 0 截断,不是向下取整!)
浮点数和 IEEE 754
https://dongyanzhang.com/posts/计算机组成原理/浮点数和-ieee-754/
作者
阿东阿言
发布于
2026-07-14
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
阿东阿言
I share my notes | projects | experience here...
公告
欢迎来到阿东阿言的博客
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
分类
标签
站点统计
文章
25
分类
3
标签
5
总字数
11,186
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
v1.0.0
文章许可
CC BY-NC-SA 4.0