浮点数最小间隔公式
浮点数最小间隔公式(float32:最小间隔≈当前数值 ×2⁻²³)详细拆解
要搞懂这个公式,核心是抓住「float32 的尾数存储规则」和「二进制有效数字的位权逻辑」—— 公式不是凭空来的,是从 IEEE 754 标准的存储结构直接推导出来的。咱们从「尾数的位权→相邻浮点数的差异→公式推导→实例验证」一步步拆,全程不跳步。
一、前置基础:先明确 float32 尾数的 “位权分布”
float32 的尾数位(M)是 23 位,且只存储「二进制归一化数的小数部分」(因为整数部分是固定的 1,即隐藏位,不用存)。
1. 归一化数的结构
所有常规 float32 数值(E 不是全 0 / 全 1)都能表示为:数值 = (1 + M) × 2^e
- 1:隐藏位(固定为 1,不存储);
- M:23 位尾数位的二进制值(比如 M=01101₂,就是 0.01101₂的小数);
- e:实际指数(= 存储的 E 值 - 127,比如 e=2、e=-4)。
2. 尾数位(M)的位权的关键
23 位尾数位 M,每一位的 “权重” 是固定的 —— 从左到右(小数点后第 1 位到第 23 位),位权依次是 2⁻¹、2⁻²、2⁻³、...、2⁻²³。举个例子:M=01101₂(仅前 5 位有值,后 18 位为 0),对应的十进制值是:0×2⁻¹ + 1×2⁻² + 1×2⁻³ + 0×2⁻⁴ + 1×2⁻⁵ + 0×2⁻⁶ + ... + 0×2⁻²³ = 0.25 + 0.125 + 0.03125 = 0.40625
这里的核心:尾数位的 “最小单位” 是最后一位(第 23 位),其权重是 2⁻²³ —— 这是尾数能表示的 “最小变化量”,也是公式中 2⁻²³ 的来源!
二、核心推导:为什么最小间隔 = 当前数值 ×2⁻²³?
“最小间隔”(ULP,Unit in the Last Place)指的是:两个相邻 float32 数值的差值(即 float32 能区分的最小数值差)。
我们分 3 步推导这个差值:
步骤 1:取两个相邻的尾数值 M₁和 M₂
因为 M 是 23 位二进制数,相邻的两个 M 值,差值就是 “尾数的最小单位”—— 也就是 2⁻²³(比如 M₁=0.000…000₂,M₂=0.000…001₂,差值 = 2⁻²³;M₁=0.111…111₂,M₂=1.000…000₂,差值还是 2⁻²³)。即:M₂ - M₁ = 2⁻²³
步骤 2:计算对应的两个浮点数的差值
两个相邻浮点数的「指数 e 是相同的」(只有尾数变化,指数不变才是相邻数),所以它们的数值分别是:数值₁ = (1 + M₁) × 2^e数值₂ = (1 + M₂) × 2^e
两者的差值(最小间隔)为:数值₂ - 数值₁ = [(1 + M₂) - (1 + M₁)] × 2^e = (M₂ - M₁) × 2^e
代入 M₂ - M₁=2⁻²³,得到:最小间隔 = 2⁻²³ × 2^e = 2^(e - 23)
步骤 3:关联 “当前数值” 与 “2^e”
当前数值的核心是 (1 + M) × 2^e,而 (1 + M) 的范围是「1 ≤ (1 + M) < 2」(因为 M 是 23 位小数,最大为 0.111…111₂≈1)。这意味着:2^e ≤ 当前数值 < 2^(e+1) —— 当前数值和 2^e 是 “同一量级” 的(比如当前数值 = 5.625=1.01101×2²,2^e=2²=4,两者量级相同)。
为了简化计算(且误差极小),我们可以用「当前数值」近似替代「2^e」(因为 1 ≤ (1 + M) < 2,替代后误差不超过 1 倍)。所以:最小间隔 ≈ 当前数值 × 2⁻²³ —— 这就是公式的最终由来!
三、关键补充:公式的 “近似” 为什么合理?
可能你会问:“用当前数值替代 2^e,会不会有误差?”—— 误差很小,完全不影响对 “精度分布” 的判断:
- 当 (1 + M)=1 时(M 全 0):当前数值 = 2^e,此时最小间隔 = 2^(e-23)= 当前数值 ×2⁻²³(完全相等);
- 当 (1 + M)≈2 时(M 全 1):当前数值≈2×2^e=2^(e+1),此时最小间隔 = 2^(e-23)= 当前数值 ×2⁻²³ ÷ 2(误差仅 1 倍);
- 实际情况中,(1 + M) 的平均值约 1.5,所以近似后的误差通常在 50% 以内 —— 对于判断 “0 附近间隔小、1 附近间隔大” 的核心逻辑,这个误差完全可以忽略。
四、实例验证:用具体数值看公式是否成立
我们用之前熟悉的 float32 数值验证,让公式落地:
实例 1:0 附近(当前数值 = 0.000001=1e-6)
- 先算 2^e:当前数值 = 1e-6,找 e 使得 2^e≈1e-6 → e≈-20(因为 2^-20≈9.54e-7,和 1e-6 量级一致);
- 按精确公式:最小间隔 = 2^(e-23)=2^(-43)≈8.7e-13;
- 按近似公式:当前数值 ×2⁻²³=1e-6 × 8.3886e-8≈8.3886e-14(注意:这里因为当前数值 = 1e-6≈1.048×2^-20,(1+M)=1.048,所以用当前数值替代 2^e 后,结果更接近实际间隔);
- 实际间隔:0.000001 和相邻 float32 数值的差约 8e-14,和公式结果一致 —— 间隔极小,精度极高。
实例 2:1 附近(当前数值 = 0.999999≈1.0)
- 2^e=2^0=1(因为当前数值≈1=1.0×2^0);
- 精确公式:最小间隔 = 2^(0-23)=8.3886e-8;
- 近似公式:1.0×8.3886e-8≈8.3886e-8(完全相等);
- 实际间隔:0.999999 和下一个 float32 数值的差约 8e-8,远大于 0 附近的间隔 —— 精度极低。
实例 3:中间值(当前数值 = 0.1)
- 2^e=2^-4=0.0625(因为 0.1=1.10011…×2^-4);
- 精确公式:最小间隔 = 2^(-4-23)=2^-27≈1.387e-8;
- 近似公式:0.1×8.3886e-8≈8.3886e-9(误差约 40%,但仍能看出间隔比 0 附近大、比 1 附近小);
- 实际间隔:0.1 的相邻 float32 差值约 1e-8,和公式结果趋势一致。
五、公式的核心意义:解释 “0 近高精、1 近低精”
从公式「最小间隔≈当前数值 ×2⁻²³」能直接看出:
- 数值越小(越靠近 0):最小间隔越小 → 能区分的小数越精细(精度高);
- 数值越大(越靠近 1):最小间隔越大 → 能区分的小数越粗糙(精度低);
这就是 float32 在 Z 缓冲区 [0,1] 区间的精度分布逻辑 —— 公式是 “现象” 的本质,“0 近高精、1 近低精” 是公式推导后的必然结果!
总结:公式拆解核心要点
- 公式来源:从 float32 的「尾数存储规则(23 位 + 隐藏位)」和「相邻数值的差值计算」推导而来;
- 2⁻²³ 的意义:尾数位的 “最小单位权重”,是尾数能表示的最小变化量;
- 近似逻辑:用 “当前数值” 替代 “2^e”(同一量级,误差小),简化后得到实用公式;
- 核心作用:定量解释浮点数的精度分布,直接说明 “0 附近精度高、1 附近精度低” 的原因。