Java 为什么是8099.99975f!=8100f

Java 为什么是8099.99975f!=8100f,java,floating-point,ieee-754,single-precision,Java,Floating Point,Ieee 754,Single Precision,编辑:我知道浮点运算不精确。算术甚至不是我的问题。加法给出了我期望的结果8099.99975f没有 所以我有一个小程序: public class Test { public static void main(String[] args) { System.out.println(8099.99975f); // 8099.9995 System.out.println(8099.9995f + 0.00025f); // 8100.0

编辑:我知道浮点运算不精确。算术甚至不是我的问题。加法给出了我期望的结果<代码>8099.99975f没有


所以我有一个小程序:

public class Test {
    public static void main(String[] args) {
        System.out.println(8099.99975f); // 8099.9995
        System.out.println(8099.9995f + 0.00025f); // 8100.0
        System.out.println(8100f == 8099.99975f); // false
        System.out.println(8099.9995f + 0.00025f == 8099.99975f); // false
        // I know comparing floats with == can be troublesome
        // but here they really should be equal in every bit.
    }
}
我写它是为了检查
8099.99975
作为IEEE 754单精度浮点写入时是否四舍五入到
8100
。令我惊讶的是,Java将它转换为
8099.9995
,当它被写成浮点文本(
8099.99975f
)时。我再次检查了我的计算和IEEE标准,但没有发现任何错误
8100
8099.99975
的距离与
8099.9995
的距离一样远,但
8100
的最后一位是
0
,这应该是正确的表示

所以我检查了Java语言规范,看看是否遗漏了什么。经过快速搜索,我发现了两件事:

  • Java编程语言要求浮点运算的行为就像每个浮点运算符将其浮点结果四舍五入到结果精度一样。不精确的结果必须四舍五入到最接近无限精确结果的可表示值;如果两个最近的可表示值相等接近,则选择其最低有效位为零的值

  • Java编程语言在将浮点值转换为整数[…]时使用向零舍入

我注意到这里没有提到浮点文字。所以我认为浮点文字可能只是两倍,当转换为浮点时,它被舍入为零,类似于浮点转换为int。这就解释了为什么
8099.99975f
四舍五入为零

我编写了上面可以看到的小程序来检查我的理论,并且确实发现,当添加两个浮点文本时,应该计算出
8100
正确的浮点值。(请注意,
8099.9995
0.00025
可以精确地表示为单个浮点数,因此不存在可能导致不同结果的舍入)这让我很困惑,因为浮点数文字和计算浮点数的行为不同,这对我来说没有太大意义,所以我在语言规范中进行了更多的研究,发现:

如果浮点文字的后缀为ASCII字母F或F[…],则它的类型为float。浮点[…]类型的元素是那些可以使用IEEE 754 32位单精度[…]二进制浮点格式表示的值


最后指出,应根据IEEE标准对文字进行四舍五入,在本例中,IEEE标准为
8100
。那么为什么是
8099.9995

十进制值
8099.99975
有九个有效数字。这超出了在浮点中可以精确表示的范围。如果使用,您将看到最接近
8099.9995
的二进制表示形式是
45fd1ff
。当您尝试添加
0.00025
时,您将遭受“意义缺失”。为了不丢失较大数字的有效位(左侧),必须将较小数字的有效位右移,以匹配较大数字的刻度(指数)。当发生这种情况时,它的值在从寄存器右端移出时变为零

Decimal     Exponent        Significand
---------   --------------  -------------------------
8099.9995   10001011 (+12)  1.11111010001111111111111
   0.00025  01110011 (-12)  1.00000110001001001101111
为了将这些数据进行相加,第二个数据必须右移24位,但单个精度浮点的有效位中只有23位。有效位消失,留下零,因此加法无效


如果您想让它工作,请切换到双精度算术。

十进制值
8099.99975
有九个有效数字。这超出了在浮点中可以精确表示的范围。如果使用,您将看到最接近
8099.9995
的二进制表示形式是
45fd1ff
。当您尝试添加
0.00025
时,您将遭受“意义缺失”。为了不丢失较大数字的有效位(左侧),必须将较小数字的有效位右移,以匹配较大数字的刻度(指数)。当发生这种情况时,它的值在从寄存器右端移出时变为零

Decimal     Exponent        Significand
---------   --------------  -------------------------
8099.9995   10001011 (+12)  1.11111010001111111111111
   0.00025  01110011 (-12)  1.00000110001001001101111
为了将这些数据进行相加,第二个数据必须右移24位,但单个精度浮点的有效位中只有23位。有效位消失,留下零,因此加法无效


如果你想这样做,就切换到双精度算术。

要认识到的关键点是,浮点数的值可以用两种不同的方法计算出来,这两种方法通常并不相等

  • 浮点数中的位给出了的值的精确二进制表示形式
  • 这里有一个浮点数的“十进制显示值”,这是一个小数位数最少的数字,它比任何其他数字都更接近该浮点数
为了理解差异,考虑指数为10001011且其意义为1.1111101000 1111111111111的数。这是8099.999511171875的精确二进制表示。但是,十进制值8099.9995的小数位数较少,并且与此浮点数相比,它更接近于任何其他浮点数。因此,8099.9995是打印该数字时显示的值

请注意,这个特定的浮点数是8100之后的下一个最低浮点数

现在考虑8099.99975。它比8100稍微接近8099.9995171875。因此,为了用单精度浮点表示它,Java将选择浮点数,它是8099.999511171875的精确二进制表示形式。如果您尝试打印它,您将看到8099.9995

最后,在单精度浮点中执行8099.9995+0.00025时,所涉及的数字是8099.999511171875和0.0002499998277053的精确二进制表示