精度丢失的计算过程
float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false
1. IEEE 754 单精度浮点数标准
单精度浮点数 (float) 在 Java 中占用 32 位,分为三个部分:
- 1 位符号位:表示正数或负数。
- 8 位指数:表示二进制科学记数法中的指数部分。
- 23 位尾数(或称为小数部分):表示有效数字,使用隐含的第一位。
浮点数使用二进制科学记数法表示,形式为:
$
(-1)^{sign} \times 1.mantissa \times 2^{exponent - 127}
$
其中 sign 表示符号,mantissa 是尾数部分,exponent 是指数。
2. 二进制近似表示
很多十进制小数(如 0.1, 1.9, 1.8)在二进制中是无限循环小数,所以无法用有限位的浮点数精确表示,最终只能存储一个近似值。这就是为什么会出现 0.100000024 和 0.099999905 这样的结果。
3. 计算具体近似值
我们现在来看具体的数值是如何被近似表示的。
1. 2.0f 和 1.9f 的近似表示
2.0f 在二进制中可以精确表示为: $ 2.0f = 1.0 \times 2^1 = 01000000000000000000000000000000 $ 所以没有误差。
1.9f 在二进制中无法精确表示,它的近似表示是: $ 1.9f \approx 1.10011001100110011001101_2 \times 2^0 $ 这个表示法在
float中近似存储为0x3fcccccd,实际值大约为1.89999997615814208984375。
2. 计算 2.0f - 1.9f
由于 1.9f 并不是精确的 1.9,而是一个略小的近似值,所以 2.0f - 1.9f 计算的结果并不是 0.1,而是:
$
2.0f - 1.89999997615814208984375 \approx 0.10000002384185791015625
$
由于浮点数只能精确存储有限的位数,最终输出的近似值为 0.100000024。
3. 1.8f 和 1.7f 的近似表示
1.8f 在二进制中也无法精确表示,近似表示为: $ 1.8f \approx 1.11001100110011001100110_2 \times 2^0 $ 这个值存储为
0x3fcccccd,近似值大约为1.80000007152557373046875。1.7f 的二进制表示近似为: $ 1.7f \approx 1.10110011001100110011010_2 \times 2^0 $ 它存储为
0x3fccccc,近似值大约为1.7000000476837158203125。
4. 计算 1.8f - 1.7f
由于 1.8f 和 1.7f 都是近似值,因此它们的差值也会有误差:
$
1.8f - 1.7f \approx 1.80000007152557373046875 - 1.7000000476837158203125 \approx 0.099999904841899871826171875
$
最终输出的近似值为 0.099999905。
4. 总结原因
- 2.0f - 1.9f = 0.100000024 是因为
1.9f的近似值略小于 1.9,导致差值略大于 0.1。 - 1.8f - 1.7f = 0.099999905 是因为
1.8f和1.7f都是近似值,它们的差值略小于 0.1。