7 浮点数
浮点运算是衡量超算性能的主要指标。浮点数本质上就是科学计数法形式的二进制实数。
01 二进制小数
小数与科学计数法
小数由整数部分与小数部分组成,可以用位权公式表达:
718.415=7×102+1×101+8×100+4×10−1+1×10−2+5×10−3718.415 = 7 \times 10^2 + 1 \times 10^1 + 8 \times 10^0 + 4 \times 10^{-1} + 1 \times 10^{-2} + 5 \times 10^{-3}718.415=7×102+1×101+8×100+4×10−1+1×10−2+5×10−3
科学计数法将一个数表示为 a×10na \times 10^na×10n 的形式,其中 1≤∣a∣<101 \leq |a| < 101≤∣a∣<10,nnn 为整数。
二进制小数的表示
二进制小数点右侧的每一位表示 2 的负幂:
∑k=−jibk×2k\sum_{k=-j}^{i} b_k \times 2^kk=−j∑ibk×2k
| 位置 | 权值 |
|---|---|
| b−1b_{-1}b−1(小数点右第 1 位) | 2−1=1/22^{-1} = 1/22−1=1/2 |
| b−2b_{-2}b−2(小数点右第 2 位) | 2−2=1/42^{-2} = 1/42−2=1/4 |
| b−3b_{-3}b−3(小数点右第 3 位) | 2−3=1/82^{-3} = 1/82−3=1/8 |
| b−jb_{-j}b−j(小数点右第 jjj 位) | 2−j2^{-j}2−j |
示例 :1011.1012=8+2+1+0.5+0.125=11.6251011.101_2 = 8 + 2 + 1 + 0.5 + 0.125 = 11.6251011.1012=8+2+1+0.5+0.125=11.625
快速转换窍门 :小数部分写成分数,分母为 2k2^k2k(kkk 为小数位数),分子为小数部分二进制的值。例如 .1012=5/8=0.625.101_2 = 5/8 = 0.625.1012=5/8=0.625
二进制小数举例
| 十进制 | 二进制 |
|---|---|
| 5 3/4 | 101.112101.11_2101.112 |
| 2 7/8 | 10.111210.111_210.1112 |
| 1 13/16 | 1.110121.1101_21.11012 |
规律:
- 小数点右移一位 = 乘 2;左移一位 = 除 2
- 0.111111...20.111111\ldots_20.111111...2 表示刚好小于 1.0 的数(即 1.0−ε1.0 - \varepsilon1.0−ε)
二进制小数的表示限制
二进制小数只能精确表示形如 x/2kx / 2^kx/2k 的数,其他值只能近似表示:
| 十进制值 | 二进制表示 |
|---|---|
| 1/3 | 0.0101010101[01]...20.0101010101[01]\ldots_20.0101010101[01]...2(无限循环) |
| 1/5 | 0.001100110011[0011]...20.001100110011[0011]\ldots_20.001100110011[0011]...2 |
| 1/10 | 0.0001100110011[0011]...20.0001100110011[0011]\ldots_20.0001100110011[0011]...2 |
这就是为什么在计算机中 0.1 + 0.2 ≠ 0.3------十进制的 0.1 在二进制中是无限循环小数,存储时必然被截断。
02 IEEE 浮点数标准
IEEE 754 标准
IEEE 754 标准于 1985 年建立(之前每个计算机制造商使用自己的浮点规则),现在被所有主流 CPU 支持。该标准面向数字运算的精确性,支持舍入、溢出等操作,定义在一组小而一致的规则上。
浮点数的数学形式
V=(−1)s×M×2EV = (-1)^s \times M \times 2^EV=(−1)s×M×2E
- 符号位 s (Sign):决定正数还是负数,数值 0 的符号位特殊处理
- 尾数 M (Significand) :一个二进制小数,通常在 [1.0,2.0)[1.0, 2.0)[1.0,2.0) 范围内
- 阶码 E (Exponent):表示 2 的幂
在二进制编码中,浮点数分为三个字段:
| 字段 | 含义 |
|---|---|
s(1 位) |
符号位 |
exp(若干位) |
阶码字段 |
frac(若干位) |
尾数字段 |
浮点数类型
| 精度 | 总位数 | 符号 | 阶码 (exp) | 尾数 (frac) |
|---|---|---|---|---|
| 单精度 (float) | 32 bits | 1 | 8 bits | 23 bits |
| 双精度 (double) | 64 bits | 1 | 11 bits | 52 bits |
| 扩展精度 (Intel only) | 80 bits | 1 | 15 bits | 63 或 64 bits |
三种表示类别
根据阶码字段 exp 的取值,浮点数分为三种类别:
阶码 exp
├── 全 0(000...0) → 非规格化数
├── 非全 0 且非全 1 → 规格化数
└── 全 1(111...1) → 特殊值
├── frac = 000...0 → ±∞
└── frac ≠ 000...0 → NaN
类别 1:规格化值(Normalized)
判断条件:exp 不为全 0 且不为全 1
- 阶码 :采用偏置(biased)表示
- E=Exp−BiasE = Exp - BiasE=Exp−Bias
- ExpExpExp 是 exp 字段的无符号值
- Bias=2k−1−1Bias = 2^{k-1} - 1Bias=2k−1−1,其中 kkk 为阶码位数(单精度 Bias = 127,双精度 Bias = 1023)
- 尾数 :M=(1.xxx...x)2M = (1.xxx\ldots x)_2M=(1.xxx...x)2
- 整数部分隐含为 1(不需要存储),frac 字段存储小数部分
- 最小值:frac 全 0 时 M=1.0M = 1.0M=1.0
- 最大值:frac 全 1 时 M=2.0−εM = 2.0 - \varepsilonM=2.0−ε
规格化值示例:float F = 15213.0
- 1521310=111011011011012=1.11011011011012×21315213_{10} = 11101101101101_2 = 1.1101101101101_2 \times 2^{13}1521310=111011011011012=1.11011011011012×213
- 尾数:M=1.11011011011012M = 1.1101101101101_2M=1.11011011011012,frac =
11011011011010000000000 - 阶码:E=13E = 13E=13,Exp=E+Bias=13+127=140=100011002Exp = E + Bias = 13 + 127 = 140 = 10001100_2Exp=E+Bias=13+127=140=100011002
- 最终编码:
0 | 10001100 | 11011011011010000000000
类别 2:非规格化值(Denormalized)
判断条件:exp = 000...0(阶码全 0)
- 阶码 :E=1−BiasE = 1 - BiasE=1−Bias(注意不是 0−Bias0 - Bias0−Bias,这是为了让非规格化值与规格化值之间平滑过渡)
- 尾数 :M=(0.xxx...x)2M = (0.xxx\ldots x)_2M=(0.xxx...x)2(隐含整数部分为 0,而不是 1)
两种特殊情况:
- exp = 0, frac = 0:表示 0(符号位决定 +0 或 -0)
- exp = 0, frac ≠ 0:表示非常接近 0.0 的数
类别 3:特殊值
判断条件:exp = 111...1(阶码全 1)
- 情况 1 :exp 全 1,frac = 0 → 表示无穷大 (+∞+\infty+∞ 或 −∞-\infty−∞),可用来表示溢出结果,如 1.0/+0.0=+∞1.0 / +0.0 = +\infty1.0/+0.0=+∞,1.0/−0.0=−∞1.0 / -0.0 = -\infty1.0/−0.0=−∞
- 情况 2 :exp 全 1,frac ≠ 0 → 表示 NaN (Not a Number),用来表示无法定义的运算结果,如 −1\sqrt{-1}−1 、∞−∞\infty - \infty∞−∞、∞×0\infty \times 0∞×0
03 示例与性质
8 位浮点数完整示例
以一个 8 位微型浮点格式为例:1 位符号 + 4 位阶码 + 3 位尾数,Bias = 23−1=72^3 - 1 = 723−1=7。
非规格化数 (exp = 0000,E=1−7=−6E = 1 - 7 = -6E=1−7=−6):
| 编码 | E | M | 值 | 说明 |
|---|---|---|---|---|
0 0000 000 |
-6 | 0/8 | 0 | 零 |
0 0000 001 |
-6 | 1/8 | 1/512 | 最小非规格化正数 |
0 0000 010 |
-6 | 2/8 | 2/512 | |
| ... | ||||
0 0000 111 |
-6 | 7/8 | 7/512 | 最大非规格化数 |
规格化数(exp 非全 0 非全 1):
| 编码 | E | M | 值 | 说明 |
|---|---|---|---|---|
0 0001 000 |
-6 | 8/8 | 8/512 | 最小规格化数 |
0 0001 001 |
-6 | 9/8 | 9/512 | |
| ... | ||||
0 0110 110 |
-1 | 14/8 | 14/16 | |
0 0110 111 |
-1 | 15/8 | 15/16 | 从下最靠近 1 |
0 0111 000 |
0 | 8/8 | 1 | |
0 0111 001 |
0 | 9/8 | 9/8 | 从上最靠近 1 |
| ... | ||||
0 1110 111 |
7 | 15/8 | 240 | 最大规格化数 |
0 1111 000 |
--- | --- | +∞+\infty+∞ | 正无穷 |
注意最大非规格化数(7/512)到最小规格化数(8/512)之间的平滑过渡 ------这正是非规格化数使用 E=1−BiasE = 1 - BiasE=1−Bias 而非 0−Bias0 - Bias0−Bias 的设计目的。
浮点数取值分布特征
- 浮点数在数轴上的分布是不均匀的:越靠近 0,可表示的数越密集;越远离 0,间距越大
- 非规格化数(Denormalized)集中在 0 附近,规格化数(Normalized)覆盖更大范围
浮点数属性(单精度 / 双精度)
| 描述 | 单精度 (float) | 双精度 (double) |
|---|---|---|
| 最小非规格化正数 | ≈1.4×10−45\approx 1.4 \times 10^{-45}≈1.4×10−45 | ≈4.9×10−324\approx 4.9 \times 10^{-324}≈4.9×10−324 |
| 最大非规格化正数 | ≈1.18×10−38\approx 1.18 \times 10^{-38}≈1.18×10−38 | ≈2.2×10−308\approx 2.2 \times 10^{-308}≈2.2×10−308 |
| 最小规格化正数 | ≈1.18×10−38\approx 1.18 \times 10^{-38}≈1.18×10−38 | ≈2.2×10−308\approx 2.2 \times 10^{-308}≈2.2×10−308 |
| 1 | 1.01.01.0 | 1.01.01.0 |
| 最大规格化正数 | ≈3.4×1038\approx 3.4 \times 10^{38}≈3.4×1038 | ≈1.8×10308\approx 1.8 \times 10^{308}≈1.8×10308 |
04 舍入与运算
浮点运算的基本思路
浮点加法和乘法的基本策略是:
- 先按精确数学运算计算出精确结果
- 再通过 " 舍入 " 将结果拟合到浮点格式能表示的最近值
x+fy=Round(x+y)x +_f y = Round(x + y)x+fy=Round(x+y)
x×fy=Round(x×y)x \times_f y = Round(x \times y)x×fy=Round(x×y)
浮点运算溢出
由于浮点数使用有限位数的二进制(单精度 32 位,双精度 64 位),运算时也会出现溢出。Ariane 5 火箭事故(1996 年)就是因为软件工程师没有考虑浮点数溢出问题,导致火箭发射后仅 37 秒解体爆炸,价值 5 亿美元的通信卫星付之一炬。
四种舍入方式
| 舍入方式 | $1.40 | $1.60 | $1.50 | $2.50 | -$1.50 |
|---|---|---|---|---|---|
| 向零舍入 (Towards zero) | $1 | $1 | $1 | $2 | -$1 |
| 向下舍入 (Round down, −∞-\infty−∞) | $1 | $1 | $1 | $2 | -$2 |
| 向上舍入 (Round up, +∞+\infty+∞) | $2 | $2 | $2 | $3 | -$1 |
| 向偶数舍入 (Nearest Even)(默认) | $1 | $2 | $2 | $2 | -$2 |
向偶数舍入能找到最接近的匹配值,且在中间值时不会产生统计偏差(其他三种用于计算上界和下界)。
向偶数舍入(Round to Nearest Even)
这是 IEEE 754 的默认舍入方案。规则是:中间值(恰好在两个可表示值正中间)应当向偶数方向舍入。
十进制示例(舍入到百分位):
- 1.2349999 → 1.23(不到中间值,向下)
- 1.2350001 → 1.24(超过中间值,向上)
- 1.2350000 → 1.24(中间值,4 是偶数,向上凑偶)
- 1.2450000 → 1.24(中间值,4 是偶数,向下凑偶)
二进制舍入
在二进制中:
- " 偶数 " 是指末位为 0
- " 中间值 " 是指舍入位右边恰好是 100...02100\ldots 0_2100...02 的形式
示例(舍入到小数点右边两位,即精度为 1/4):
| 原值 | 二进制 | 舍入后 | 结果 | 操作 |
|---|---|---|---|---|
| 2 3/32 | 10.00011210.00011_210.000112 | 10.00210.00_210.002 | 2 | 不到中间值,向下 |
| 2 3/16 | 10.00110210.00110_210.001102 | 10.01210.01_210.012 | 2 1/4 | 超过中间值,向上 |
| 2 7/8 | 10.11100210.11100_210.111002 | 11.00211.00_211.002 | 3 | 中间值,向上凑偶 |
| 2 5/8 | 10.10100210.10100_210.101002 | 10.10210.10_210.102 | 2 1/2 | 中间值,向下凑偶 |
浮点数乘法
(−1)s1M1⋅2E1×(−1)s2M2⋅2E2=(−1)sM⋅2E(-1)^{s_1} M_1 \cdot 2^{E_1} \times (-1)^{s_2} M_2 \cdot 2^{E_2} = (-1)^s M \cdot 2^E(−1)s1M1⋅2E1×(−1)s2M2⋅2E2=(−1)sM⋅2E
精确结果:
- 符号位:s=s1⊕s2s = s_1 \oplus s_2s=s1⊕s2(异或)
- 尾数:M=M1×M2M = M_1 \times M_2M=M1×M2
- 阶码:E=E1+E2E = E_1 + E_2E=E1+E2
调整步骤:
- 如果 M≥2M \geq 2M≥2,将 MMM 右移一位,E=E+1E = E + 1E=E+1
- 如果 EEE 超出可表示范围,则溢出
- 将 MMM 舍入到 frac 的位数范围
浮点数加法
加法的四个步骤:
- 对阶 :小阶向大阶对齐------将阶码小的那个数的尾数右移 ∣ΔE∣|\Delta E|∣ΔE∣ 位,同时阶码加上 ∣ΔE∣|\Delta E|∣ΔE∣(值不变但精度变差)
- 尾数相加:对两个对阶后的浮点数执行尾数加法
- 规格化并舍入 :如果结果尾数不是规格化形式(1.xxx1.xxx1.xxx),需要调整并舍入
- 判断溢出:根据阶码判断是否超出可表示范围
浮点加法的数学特性
与阿贝尔群比较:
- 有交换性 :a+b=b+aa + b = b + aa+b=b+a
- 没有结合性 (由于舍入):(3.14+1e10)−1e10=0(3.14 + 1\text{e}10) - 1\text{e}10 = 0(3.14+1e10)−1e10=0,而 3.14+(1e10−1e10)=3.143.14 + (1\text{e}10 - 1\text{e}10) = 3.143.14+(1e10−1e10)=3.14
- 单调性 :a≥b⇒a+c≥b+ca \geq b \Rightarrow a + c \geq b + ca≥b⇒a+c≥b+c(除了涉及 infinities 和 NaN 的情况)
浮点乘法的数学特性
- 可交换 :a×b=b×aa \times b = b \times aa×b=b×a
- 不可结合 :a×b×c≠a×(b×c)a \times b \times c \neq a \times (b \times c)a×b×c=a×(b×c)
- 不具备分配性 :a×(b+c)≠a×b+a×ca \times (b + c) \neq a \times b + a \times ca×(b+c)=a×b+a×c
- 单调性 :a≥ba \geq ba≥b 且 c≥0⇒a×c≥b×cc \geq 0 \Rightarrow a \times c \geq b \times cc≥0⇒a×c≥b×c(除了 infinities 和 NaN)
05 C 语言中的浮点数
两种浮点类型
| 类型 | 精度 | 位数 |
|---|---|---|
float |
单精度 | 32 bits |
double |
双精度 | 64 bits |
类型转换规则
在 int、float、double 之间转换时,位级表示会改变:
| 转换方向 | 行为 |
|---|---|
double/float → int |
向零舍入;对于无法表示或超出范围的值,行为未定义 |
int → double |
能够保留精确值(因为 double 有 52 位尾数,足以表示 32 位 int) |
int → float |
数字不会溢出,但可能被舍入(float 只有 23 位尾数,不足以精确表示所有 32 位 int) |
浮点数谜题
给定 int x; float f; double d;(假设 d 和 f 都不是 NaN),判断以下表达式是否总为真:
| 表达式 | 结论 | 原因 |
|---|---|---|
x == (int)(float)x |
不一定 | float 只有 23 位尾数,大 int 值转 float 会丢失精度 |
x == (int)(double)x |
总为真 | double 有 52 位尾数,能精确表示任何 32 位 int |
f == (float)(double)f |
总为真 | double 精度高于 float,不会丢失信息 |
d == (float)d |
不一定 | double 转 float 会丢失精度 |
f == -(-f) |
总为真 | 取负只翻转符号位,两次翻转恢复原值 |
2/3 == 2/3.0 |
不成立 | 左边是整数除法结果 0,右边是浮点除法结果 0.666... |
d * d >= 0.0 |
总为真 | 平方结果非负(即使溢出到 +∞+\infty+∞ 也 ≥0\geq 0≥0) |
(d+f)-d == f |
不一定 | 浮点加法没有结合性,舍入可能导致精度丢失 |
本节小结
- 二进制小数 :小数点右侧每一位代表 2−1,2−2,...2^{-1}, 2^{-2}, \ldots2−1,2−2,...;只能精确表示 x/2kx/2^kx/2k 形式的数,1/3、1/5、1/10 等在二进制中都是无限循环。
- IEEE 754 标准 :浮点数 = (−1)s×M×2E(-1)^s \times M \times 2^E(−1)s×M×2E,由符号位、阶码、尾数三个字段编码。单精度 32 位(1+8+23),双精度 64 位(1+11+52)。
- 三种类别 :规格化值(阶码非全 0 非全 1,隐含前导 1)、非规格化值(阶码全 0,隐含前导 0,用于表示极小值和 0)、特殊值(阶码全 1,表示 ±∞\pm\infty±∞ 或 NaN)。
- 舍入:默认向偶数舍入(Round to Nearest Even),中间值向偶数方向凑,避免统计偏差。
- 浮点运算特性 :加法和乘法都满足交换律,但不满足结合律和分配律------这是舍入导致的根本性质,编程时必须注意运算顺序。
- C 语言转换:int → double 精确;int → float 可能舍入;float/double → int 向零截断且可能溢出。