Java(IEEE 754)中浮点数(float,double)的计算精度

The calculation accuracy of floating-point numbers (float, double) in Java (IEEE 754)

我在 jshell of JDK 11 中尝试了以下计算:

jshell> 0.1 + 0.2 == 0.3
 ==> false

这个我能理解return。毕竟0.1、0.2、0.3都不能用二进制准确表示。

但是当我切换到float而不是double时,我惊讶地发现jshell的return是:

jshell> 0.1f + 0.2f == 0.3f
 ==> true

这与我一直以来的理解相悖。 所以我尝试让jshell直接计算:

jshell> 0.1 + 0.2
 ==> 0.30000000000000004

jshell> 0.1f + 0.2f
 ==> 0.3

确实,如果我使用float数据类型,似乎可以准确计算出结果为0.3.

但是为什么呢?如果double不能准确表示和计算0.1 + 0.2,那为什么float可以呢?

如果我的测试有任何错误,请指出。 先谢谢了!

即使是浮点数,这个数字实际上也不是 0.3。

    public static void main (String[] args) throws java.lang.Exception
    {
        float a = 0.1f;
        float b = 0.2f;
        float c = 0.3f;
        float d = a + b;
 
        System.out.println( new BigDecimal(c) + ", " + new BigDecimal(d));
    }

那就出来了。

0.300000011920928955078125, 0.300000011920928955078125

您发现了错误相互抵消的情况。

这类似于使用十进制系统,加上 1/3 + 2/3。 1/3 大约是 0.333,2/3 大约是 0.667,所以当您将两者相加时,您会得到 1.0,即使它们都是近似值。

每次执行浮点运算时,理想的实数运算结果都会四舍五入为浮点格式中可表示的最接近值,使用适用于该运算的舍入方法(最常见的是舍入到-最接近偶数)。

有时四舍五入的方向会取消之前的四舍五入。有时四舍五入的方向会加剧之前的四舍五入。

将源文本 0.1 转换为 double 是一个浮点运算。它产生 0.1000000000000000055511151231257827021181583404541015625,因此四舍五入使结果更大。

0.2 转换为 double 生成 0.200000000000000011102230246251565404236316680908203125,再次变大。

0.3 转换为 double` 会产生 0.299999999999999988897769753748434595763683319091796875,因此四舍五入会使结果变小。

加上前两个,0.1000000000000000055511151231257827021181583404541015625 和 0.200000000000000011102230246251565404236316680908203125,产生 0.3000000000000000444089209850062616169452667236328125。这里四舍五入再次增加了结果。

0.1f 转换为 float 产生 0.100000001490116119384765625.

0.2f 转换为 float 产生 0.20000000298023223876953125.

0.3f 转换为 float 生成 0.300000011920928955078125。在这种情况下,由于 float 中可表示的数字少于 double 中可表示的数字,下一个低于 0.3 的 float 值比 0.300000011920928955078125 更远离 0.3。因此,将 0.3f 转换为 float 会向上取整,即使将 0.3 转换为 double 也会向下取整。

将这些 float 中的前两个值 0.100000001490116119384765625 和 0.20000000298023223876953125 相加,得到 0.300000011920928955078125。由于这与转换 0.3f 的结果相同,因此 0.1f + 0.2f == 0.3f 的计算结果为真。

另一件需要注意的事情是 Java 的默认浮点数显示会产生足够的有效数字来唯一区分其浮点格式中的值。这意味着,当 Java 显示数字“0.3”时,并不意味着浮点值是 0.3。这意味着浮点值比该格式的任何其他值更接近 0.3,因此打印“0.3”足以识别它。

这意味着当为double打印“0.3”时,实际double值为0.299999999999999988897769753748434595763683319091796875,但是,当为float打印“0.3”时,实际 float 值为 0.300000011920928955078125。 Java 并非旨在向您显示浮点数的真实值。