IEEE 754 浮点数转换
浮点数在内存里不是直接存小数点,而是拆成「符号 + 指数 + 尾数」三块。看穿这层表示,很多精度问题就有答案了。
两个框是联动的:改十进制会更新位模式,改位模式也会反解出十进制。位模式按大端书写。
结果
3DCCCCCD
三个域怎么切
符号位单独一位;指数域存的是「指数加偏置」后的无符号整数;尾数域存的是小数部分, 规格化数里整数位的 1 被省略不存,靠指数域非零来隐含。
- 符号位:0(正)
- 指数域:01111011 = 123
- 实际指数:-4(偏置 127)
- 尾数域:10011001100110011001101
- 尾数(含隐含位):1.10011001100110011001101₂ ≈ 1.60000002
- 按位还原的值:1.60000002 × 2^-4
组合方式:值 = (1 + 尾数小数部分) × 2 的实际指数,符号位单独乘上去。 偏置的作用是让指数域变成无符号整数,省掉指数的符号位比较逻辑。
这个数存得准吗
存进去再读出来差了 1.490e-9,说明十进制小数转成二进制是无限循环的,只能存最近的近似值
能精确表示的十进制小数只有那些分母是 2 的幂的(0.5、0.25、0.125…)。0.1、0.2、0.3 都不行, 所以 0.1 + 0.2 在浮点下不等于 0.3。涉及金额就一律用整数分或十进制类型,别用浮点做累加。
单精度的有效数字到底有多少
单精度的尾数有 23 位,加上隐含的整数位是 24 位,换算成十进制约 7 位多一点(24 × log₁₀2 ≈ 7.22)。 双精度尾数 52 位,约 15 到 16 位十进制有效数字。
| 参数 | 单精度 | 双精度 |
|---|---|---|
| 总位数 | 32 位(4 字节) | 64 位(8 字节) |
| 指数域 / 偏置 | 8 位 / 127 | 11 位 / 1023 |
| 尾数域 | 23 位 | 52 位 |
| 十进制有效数字 | 约 7 位 | 约 15 到 16 位 |
| 最小规格化数 | 约 1.18e-38 | 约 2.23e-308 |
| 最大有限值 | 约 3.40e38 | 约 1.80e308 |
单精度能连续表示所有整数的上界是 2²⁴ = 16777216。超过这个数之后相邻两个浮点数之间的间隔就大于 1, 加一可能完全没有效果。上面的预设按钮里有这一项,可以直接点开看尾数域是什么样。
浮点与补码的常见坑
补码让减法变成加法。补码最大的好处是不需要单独的减法电路:a - b 直接算成 a + (-b的补码),硬件上加法器就够了。而且零只有一种表示,不会出现正零和负零比较不相等的情况。反码和原码都有正负零,还要单独处理进位回卷,所以现代 CPU 一律用补码。
最小负数没有对应的正数。8 位补码范围是 -128 到 127,不是对称的 -127 到 127。原因是要给零留一种表示,于是负半边多出一个。这导致 -128 取相反数还是 -128(溢出回卷),取绝对值也是负数。写代码时要小心 abs(INT_MIN) 这类陷阱。
0.1 在二进制里存不精确。十进制小数转二进制要不断乘二取整,0.1 会得到无限循环的小数,单精度只能存最近似的值(约 0.100000001490116)。所以 0.1 + 0.2 不等于 0.3,做金额计算要用整数分或专门的十进制类型,不能直接用浮点。
浮点比较不能直接用等号。因为存在舍入误差,应该比较两个数之差的绝对值是否小于容差。另一个坑是大数吃小数:单精度只有约 7 位有效数字,1e8 加上 1 还是 1e8,做累加时如果量级相差太大会把小数项整个丢掉。
非规格化数换来了渐进下溢。当指数域全为零时,尾数不再有隐含的 1,数值可以一直小到 2⁻¹⁴⁹(单精度)。这让「非常小的数相减是否为零」这件事有了答案,而不是突然掉到零。代价是处理非规格化数在部分硬件上会明显变慢。