浮点数可以理解为计算机里的“二进制科学计数法”。 IEEE 754 规定了符号、指数、小数部分的存储方式,也规定了零、无穷大和 NaN 等特殊值。
例如,十进制的 ;二进制的 。通过调整指数,浮点数能在有限位数内兼顾很大和很小的数,但并非每个实数都能精确表示。
IEEE 754 的字段
常见的二进制格式如下:
| 格式 | 总位数 | 符号位 | 指数位 | 小数位 | 偏置值 Bias | 有效二进制位 |
|---|---|---|---|---|---|---|
单精度 float |
32 | 1 | 8 | 23 | 127 | 24 |
双精度 double |
64 | 1 | 11 | 52 | 1023 | 53 |
单精度的排列是 S | EEEEEEEE | FFFFFFFFFFFFFFFFFFFFFFF。 表示正, 表示负,因此符号因子为 。
偏置指数与隐藏位
指数可能为负,但指数位按无符号数存储。对规格化数,存储的指数 与真实指数的关系是:
例如单精度的真实指数为 时,存储 ;真实指数为 时,存储 。
规格化后的二进制有效数字写作 。最高位的 1 恒定,无需实际存储,这就是隐藏位;只存小数点后的 。因此单精度的 23 位小数可提供 24 位二进制有效精度,约 7 位十进制有效数字;双精度为 53 位,约 15~16 位十进制有效数字。
重点: 仅当指数位既不全为 0、也不全为 1 时,才使用隐藏的
1。此时数值为
例如 ,其中符号位为 1,真实指数为 2。
编码实例:将 5.75 写成单精度浮点数
- 转成二进制:。
- 符号位 ;指数存储值 。
- 隐去有效数字开头的
1.,小数部分0111补足 23 位,得到01110000000000000000000。
拼接后得到:
0 | 10000001 | 01110000000000000000000
对应的 32 位十六进制表示是 0x40B80000。
特殊编码
单精度和双精度的指数、尾数组合对应如下:
| 单精度指数 | 单精度尾数 | 双精度指数 | 双精度尾数 | 表示的对象 |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 0 | 非 0 | 0 | 非 0 | 正负非规格化数 |
| 1~254 | 任意数 | 1~2046 | 任意数 | 正负浮点数 |
| 255 | 0 | 2047 | 0 | 正负无穷 |
| 255 | 非 0 | 2047 | 非 0 | NaN(非数) |
正负符号由单独的符号位决定,因此表中的 0 也有 和 两种编码。中间一行的正负浮点数属于规格化数,使用隐藏的 1。
正负零、无穷大与 NaN
正零和负零只差符号位,通常比较为相等;在遵循 IEEE 754 的浮点环境中,,而 。指数全为 1 且小数全为 0 时表示无穷大,符号仍由 决定。
NaN 表示没有正常实数结果的情况,例如实数浮点计算中的 。它与自身比较也不相等:NaN == NaN 为假。判断 NaN 应使用语言提供的函数,如 C/C++ 的 isnan(x),而不是比较 x == NAN。
非规格化数与渐进下溢
指数全为 0 且小数非 0 时,隐藏位改为 0,指数按 计算:
单精度对应的指数为 。非规格化数填补了最小规格化数与 0 之间的空隙,使数值可以逐渐接近 0,这称为渐进下溢。
精度限制与比较
为什么 0.1 不能精确存储?
十进制的 转为二进制是无限循环小数:
小数位有限,存储时必须舍入。因此 0.1 + 0.2 在常见双精度计算中可能得到 0.30000000000000004:参与计算的 0.1 和 0.2 本来就是近似值。
相邻浮点数的间距并不固定
指数越大,相邻可表示数的间距通常越大。例如单精度在 附近已无法表示下一个整数 。在常见的舍入方式下,16777216.0f + 1.0f 仍可能得到 16777216。
比较浮点结果
对计算得到的浮点数,0.1 + 0.2 == 0.3 可能为假。可根据问题允许的误差,检查 ; 需要结合数值规模选择,不能在所有场景固定使用同一个值。
看来我的智力确实是超进化了,
我记得大二上课我都没搞明白浮点数,
现在一下就明白了,哈哈。😄
