FP32表达的数字范围及由来

一、FP32数据类型表达的数据范围

在计算机科学和 IEEE 754 标准中,FP32(单精度浮点数,Single-precision floating-point format)使用 32 个比特(bits)来表示。

根据其位结构(1 位符号位 + 8 位指数位 + 23 位尾数位),FP32 的数据表达范围如下:

1.1. 核心数值范围

FP32 能够表达的数值跨度极大,主要分为以下两个指标:

  • 最大正值(Max Positive Value)

    \[\approx 3.4028235 \times 10^{38}\]

    (这是由指数位全为 1,尾数位除最后一位全为 1 时计算得出的边界)。

  • 最小正值(非零最小绝对值)

    • 规格化数(Normalized)\(\approx 1.17549435 \times 10^{-38}\)
    • 非规格化数(Subnormal,精度较低)\(\approx 1.40129846 \times 10^{-45}\)

1.2. 精度限制(有效数字)

FP32 的有效数字位数大约是 6 到 9 位十进制数字

通常在工程实践中,我们认为 FP32 具有 7 位可靠的十进制精度。这意味着如果一个数字超过 7 位(例如 1234567.89),末尾的数字可能会因为舍入误差而变得不准确。

二、这个表达范围是怎么计算来的?

要理解 FP32(单精度浮点数)的表达范围,我们需要拆解 IEEE 754 标准定义的 32 位二进制结构

FP32 的 32 个比特(bits)被分为三部分:

  1. 符号位 (Sign, \(S\)):1 位(0 代表正,1 代表负)。
  2. 指数位 (Exponent, \(E\)):8 位(决定数值的大小级数,即范围)。
  3. 尾数位 (Fraction/Mantissa, \(M\)):23 位(决定数值的精确度)。

其数值计算公式为:

\[\text{Value} = (-1)^S \times (1 + M) \times 2^{(E - 127)}\]


2.1. 最大正值的计算(\(\approx 3.4 \times 10^{38}\)

要得到最大的数,我们需要让指数 \(E\) 和尾数 \(M\) 都尽可能大:

  • 指数位 \(E\):8 位二进制全为 1 时是 255。但 IEEE 754 规定 255 留给特殊值(如 \(\infty\) 和 NaN)。因此,最大的可用指数是 254

    • 代入偏移量(Bias = 127):\(E - 127 = 254 - 127 = 127\)
    • 这意味着最大数大约是 \(2^{127}\)
  • 尾数位 \(M\):23 位全为 1。由于公式中是 \((1 + M)\),当 \(M\) 无限接近 1 时,该部分接近 2

  • 最终计算

    \[\text{Max} \approx 2 \times 2^{127} = 2^{128}\]

    用十进制换算:\(2^{128} \approx 3.4028235 \times 10^{38}\)


2.2. 最小正值的计算(\(\approx 1.17 \times 10^{-38}\)

这里指“规格化”情况下的最小非零正数,即让指数尽可能小:

  • 指数位 \(E\)\(E\) 不能全为 0(全 0 留给了“非规格化数”或数字 0)。因此最小的 \(E\)1

    • 代入偏移量:\(E - 127 = 1 - 127 = -126\)
  • 尾数位 \(M\):全为 0。此时 \((1 + M) = 1\)

  • 最终计算

    \[\text{Min} = 1 \times 2^{-126} \approx 1.17549435 \times 10^{-38}\]


2.3. 为什么是 7 位有效数字?

这是由 23 位尾数 决定的。

  • 在二进制里,23 位尾数加上隐藏的整数位“1”,总共有 24 位精度。
  • 计算十进制精度:\(\log_{10}(2^{24}) \approx 24 \times 0.3010 \approx 7.22\)
  • 这意味着 FP32 可以稳定地表示 7 位十进制有效数字

三、指数位为什么要减去一个偏移量127?

这是一个非常经典且精妙的计算机底层设计问题。简而言之:减去 127 是为了用“无符号”的二进制数,同时表示出“正指数”和“负指数”。

如果不减去偏移量,8 位指数位(0-255)只能表示正数,那我们就没法表示像 \(10^{-38}\) 这样极小的微观数字了。


3.1. 为什么不直接用“正负号位”?

你可能会问:既然符号位(Sign)可以决定整个数字的正负,为什么不在指数位里也划出 1 位来当正负号?

  • 原因:为了方便排序(比较大小)。
  • 如果指数位带符号(补码表示),负指数的二进制开头的数字会比正指数大(比如补码中 -1 的二进制领先于 1)。
  • IEEE 754 的天才设计:通过加上一个偏移量(Bias),将原本范围是 \([-126, +127]\) 的指数,整体平移到了 \([1, 254]\) 这个全正数区间。
  • 结果:计算机在比较两个浮点数大小时,可以直接从高位到低位像比较整数一样进行位比较,而不需要先去处理指数的正负逻辑,这极大提升了硬件执行效率。

3.2. 偏移量为什么是 127?

指数位一共有 8 位,能表示的整数个数是 \(2^8 = 256\) 个(0 到 255)。

为了让正负指数的范围几乎对称,我们取中间值作为“零点”:

  • 公式\(\text{Bias} = 2^{(k-1)} - 1\)(其中 \(k\) 是指数位数)。
  • 对于 FP32,\(k=8\),所以 \(\text{Bias} = 2^7 - 1 = 127\)

映射关系如下:

  • 二进制存储为 01111111 (127) \(\rightarrow\) 实际指数为 \(127 - 127 = 0\)
  • 二进制存储为 11111110 (254) \(\rightarrow\) 实际指数为 \(254 - 127 = 127\)(最大值)
  • 二进制存储为 00000001 (1) \(\rightarrow\) 实际指数为 \(1 - 127 = -126\)(最小值)

3.3. 特殊值的保留

你可能注意到 0 和 255 被跳过了:

  • 全 0 (00000000):留给数字 0 或者“非规格化数”(极小的数)。
  • 全 1 (11111111):留给 Inf(无穷大)或者 NaN(不是一个数字)。

四、M 最大时为什么无限接近于1

要理解为什么 \(M\) 无限接近 \(1\),我们需要把这 23 位“尾数”看作是二进制的小数部分

在 FP32 的公式 \(\text{Value} = 1.M \times 2^{E-127}\) 中,那个 \(1\) 是隐含的(Hidden Bit),而 \(M\) 实际上是小数点后面的数字


4.1. 二进制小数的权重

在十进制中,小数点后第一位权重是 \(10^{-1} (0.1)\),第二位是 \(10^{-2} (0.01)\)

二进制中,权重是按 \(2\) 的负幂次递减的:

  • 第 1 位:\(2^{-1} = 0.5\)
  • 第 2 位:\(2^{-2} = 0.25\)
  • 第 3 位:\(2^{-3} = 0.125\)
  • ...
  • 第 23 位:\(2^{-23} \approx 0.000000119\)

4.2. 当 23 位全为 1 时的求和

当尾数位 \(M\) 的 23 个比特位全部填满 \(1\) 时,它的数学表达式是一个等比数列求和

\[M = \frac{1}{2} + \frac{1}{4} + \frac{1}{8} + \dots + \frac{1}{2^{23}}\]

根据等比数列求和公式 \(S_n = a_1 \frac{1-r^n}{1-r}\)

\[M = \frac{1}{2} \times \frac{1 - (1/2)^{23}}{1 - 1/2} = 1 - \frac{1}{2^{23}}\]

计算结果:

\[M = 1 - 0.0000001192... = \mathbf{0.9999998807...}\]

所以,加上隐含的整数位 \(1\)\((1 + M)\) 的最大值就是 \(1.9999998807...\),极其接近 \(2\) 但永远不会达到 \(2\)


4.3. 为什么要这样设计?(隐含位 1 的妙处)

你可能会问:既然接近 2,为什么不直接让它到 2?

这其实是为了唯一性节省空间

  • 科学计数法的规范化:就像十进制科学计数法要求开头必须是 \(1 \sim 9\) 之间(例如 \(1.23 \times 10^5\)),二进制要求开头必须是 \(1\)(例如 \(1.011 \times 2^3\))。
  • 白捡 1 位精度:既然规格化浮点数的开头第一位永远是 1,那我们干脆不在内存里存这个 \(1\),只存小数点后面的部分。
  • 效果:原本 23 位的空间,实际发挥了 24 位的作用。

五、7 位有效数字到底是怎么来的?

这是一个非常深刻的数学跨界问题:如何将“二进制的精度”翻译成“十进制的精度”?

简单来说,这行公式是在计算:一个由 24 位二进制(0 或 1)组成的数字,最多能表达多少种不同的状态?而这些状态如果换成十进制,相当于多少位有效数字?

我们可以通过以下三个步骤拆解这个逻辑:


5.1. 状态总量的对等性

在二进制(Base-2)里,24 位比特(bits)能表示的不同数值状态总数是:

\[2^{24} = 16,777,216\]

在十进制(Base-10)里,如果我们有 \(N\) 位有效数字,能表示的状态总数是:

\[10^N\]

我们要找的“十进制精度” \(N\),就是让这两个表达能力相等的那个值:

\[10^N = 2^{24}\]


5.2. 为什么使用对数(Log)?

为了求出 \(N\),我们需要对等式两边取以 10 为底的对数:

\[\log_{10}(10^N) = \log_{10}(2^{24})\]

根据对数的性质(幂可以提到前面),等式变为:

\[N = 24 \times \log_{10}(2)\]

因为 \(\log_{10}(2) \approx 0.30103\)(这代表 1 位二进制大约相当于 0.301 位十进制),所以:

\[N \approx 24 \times 0.30103 \approx 7.2247\]

这意味着 24 位二进制的精确度,等效于 7.22 位的十进制精确度。


5.3. “7.22 位”在现实中意味着什么?

这里的 \(7.22\) 位是一个理论值,在实际应用中:

  • 7 位是“绝对稳定”的:由于 \(7.22 > 7\),任何 7 位的十进制数(比如 9,999,999)转换成二进制再转回来,都不会丢失信息。
  • 第 8 位是“不可靠”的:因为达不到 8 位(\(7.22 < 8\)),如果你有一个 8 位的数字(比如 12,345,678),FP32 的 24 位二进制可能无法区分它与 12,345,679 之间的微小差别。

浮点数在数轴上并不是绝对连续的。 要解答你的疑问,我们需要区分两个概念:“表达的范围”*和*“表达的精度(步长)”

5.4. 继续深入学习23个尾数位

但这23位不是小数部分吗?它们能表示的最大数就是全为1时,这时翻译成十进制就是0.999多;表示的最小数字就是全为0时,翻译成十进制时就是0。

这23位能连续地表达出0.000000000001(其中有22个0)到\[M = 1 - 0.0000001192... = \mathbf{0.9999998807...}\] 吗?

1. 这 23 位能“连续”表达吗?

答案是:不能。 它们是离散的。

因为 \(M\) 只有 23 位,它只能表示 \(2^{23} = 8,388,608\) 个不同的值。

  • 最小的步长(精细度):二进制下是 \(2^{-23}\)
  • 对应的十进制步长\(1 \div 8,388,608 \approx 0.0000001192...\)

这意味着,在 \(1.0\)\(2.0\) 之间,FP32 只能表达类似下面的数值:

  • \(1.0000000000...\) (全 0)
  • \(1.0000001192...\) (最后一位是 1)
  • \(1.0000002384...\) (最后一位是 2)
  • ...
  • 你提到的 \(0.000000000001\)(12位小数)是表达不出来的。 因为它比 FP32 能表示的最小步长还要小得多。

2. 关于“7 位有效数字”的真正含义

你之前疑惑的 \(\log_{10}(2^{24})\) 算出来的 \(7.22\),其实是在告诉你:在 1 到 10 之间,这 23 位尾数能把这个区间切成多细?

由于尾数能表达 800 多万种状态,它能保证:

任何一个 7 位以内的十进制小数(比如 \(1.234567\)),在 FP32 的这 800 多万个“格子”里,都能找到一个极其接近的二进制对应点。

但如果你想要表示 12 位的小数(比如 \(1.000000000001\)),FP32 的“格子”就太粗了,它会强制把你这个数“四舍五入”到最近的一个格子上(通常就是 \(1.0\))。


3. 浮点数的“贫富差距”:动态步长

这是浮点数最神奇的地方。由于公式里有 \(2^{E-127}\)随着数字变大,刻度会越来越粗。

  • 在数字很小时(指数 \(E\) 很小):步长非常精细。比如在 \(10^{-38}\) 附近,它可以区分极其微小的差别。
  • 在数字很大时(指数 \(E\) 很大):步长变得极其粗旷。
    • 比如当数字达到 \(2^{24}\)(即 \(16,777,216\))时,步长变成了 1
    • 这意味着:在 FP32 里,\(16,777,216 + 1\) 依然等于 \(16,777,216\)。它已经无法表示“加 1”这个操作了,因为 \(1\) 已经超出了它的 23 位有效精度范围!

FP32表达的数字范围及由来
https://jiangsanyin.github.io/2026/03/19/FP32表达的数字范围及由来/
作者
sanyinjiang
发布于
2026年3月19日
许可协议