我认为IEEE-754数字可以分为三个主要类别:特殊数字、正常数字和次正常数字。这些类别基于指数的值,并且每个类别中还有一些子结构。特殊数字具有最大的指数值,次正常数字的指数是最小的,而正常数字则介于两者之间。我们可以用一个表格来总结这些内容(这里的具体值是针对单精度
float
的,正如你所问的)。
指数 |
尾数 |
类别 |
调整后的尾数 |
调整后的指数 |
FF |
非零 |
NaN |
* |
n/a |
FF |
0 |
无穷大 |
n/a |
n/a |
01 – FE |
任意值 |
规格化数 |
(1)000000 – (1)7fffff |
-126 – +127 |
00 |
非零 |
非规格化数 |
000000 – 7fffff |
-126 |
00 |
0 |
零 |
0 |
n/a |
关键在于:
- 普通数的有效数字为24位(通常称为“尾数”),其中前导位始终为1(因此是隐式的),指数范围为-126到+127(即
0x01
到0xfe
或1到254,减去偏差127)。
- 亚正常数的有效数字为23位,其中前导位不一定为1(因此是显式的),指数为-126。
现在,你可能会认为对于亚正常数来说,由于原始指数为0且指数偏差为127,实际指数应该为-127。(我也是这么想了很长时间。)但这将在亚正常数中留下一个空缺。因此,亚正常数的指数为-126,比您预期的高1,并最终与普通数中最小的指数相匹配。
那么这些范围是如何工作的呢?
对于普通数,最大的原始尾数是
0x7fffff
,加上隐含的1位后变成
0xffffff
,表示为分数是
0x1.fffffe
,或者等于1.99999988079071044921875。最小的原始尾数是
0x000000
,加上隐含的1位后变成
0x800000
,表示为分数是
0x1.000000
,或者等于1.0。
对于亚正常数,最大的原始尾数是
0x7fffff
,表示为分数是
0x0.fffffe
,或者等于0.99999988079071044921875。最小的原始尾数是
0x000001
,表示为分数是
0x0.000002
,或者等于0.00000011920928955078125。
将这些信息与最大和最小指数值结合起来,我们得到:
阈值 |
派生 |
十进制 |
十六进制 |
最大正常值 |
1.99999988 × 2127 |
3.4028234663852885981e+38 |
0xf.fffff0E+31 |
最小正常值 |
1.0 × 2-126 |
1.175494350822287508e-38 |
0x4.000000E-32 |
最大次正常值 |
0.99999988 × 2-126 |
1.175494210692441075e-38 |
0x3.fffff8E-32 |
最小次正常值 |
0.000000119 × 2-126 |
1.401298464324817071e-45 |
0x8.000000E-38 |
所以当你听到最小的
float
是1.18×10
-38时,很明显有人在谈论最小的
普通数字,并忽略了次标准数的存在。正如你所看到的,最小的次标准数要小得多。
在这个表中,我们还可以看到为什么亚规格数的指数必须是-126而不是-127。亚规格数应该覆盖最小正常数和零之间的范围。指数为-126时,它们可以均匀且良好地完成这项工作。另一方面,如果亚规格数的指数为-127,则最大的亚规格数将是0.9999998×2
-127=5.877471053462205377e-39或
0x1.fffffcE-32
,已经接近零(就这样说),其余的亚规格数被挤在下面,留下一个“大”间隙,在1.175e-38和5.877e-39之间。维基百科有一张
nice picture,来自“subnormal number”页面,说明了亚规格数填补了靠近0的间隙的方式。
另请参见
this question,了解更多关于IEEE-754浮点值如何构造的信息。
注释:在本答案中,我使用类似于
0x1.fffffe
的符号表示十六进制小数,当然这不是您的C编译器可以接受的。而
0xf.fffffE+31
是十六进制科学计数法,其中指数是16的幂,并且
E
不是位于有效数字部分的十六进制数字。这有点像
printf
/
scanf
格式
%a
,尽管
%a
使用
p
来标记其指数,该指数是2的幂次方。