1

编辑:我知道浮点运算不准确。算术甚至不是我的问题。加法给出了我预期的结果。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.
    }
}

我写它是为了检查写成 IEEE 754 单精度浮点数时8099.99975是否四舍五入。8100令我惊讶的是,Java 将其转换8099.9995为浮点字面量 ( 8099.99975f)。我再次检查了我的计算和 IEEE 标准,但没有发现任何错误。8100与 as 相距很远,8099.999758099.9995最后一点81000应该使其成为正确的表示形式。

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

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

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

我注意到这里没有提到浮点文字。所以我认为浮点文字可能只是双精度数,当转换为浮点数时,类似于浮点数到整数转换时四舍五入为零。这可以解释为什么8099.99975f四舍五入为零。

我编写了您可以在上面看到的小程序来检查我的理论,并且确实发现当添加两个应该导致8100正确浮点数的浮点文字时,会被计算出来。(请注意,8099.9995and0.00025可以完全表示为单个浮点数,因此没有可能导致不同结果的舍入)这让我感到困惑,因为浮点文字和计算浮点的行为不同对我来说没有多大意义,所以我挖了语言规范更多,发现了这个:

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

这最终表明文字应该根据 IEEE 标准进行四舍五入,在这种情况下是8100. 那么为什么会这样8099.9995呢?

4

2 回答 2

5

要实现的关键点是浮点数的值可以通过两种不同的方式计算出来,它们通常并不相等。

  • 浮点数中的位给出了确切的二进制表示的值。
  • 浮点数有“十进制显示值”,即小数位数最少的数字,比任何其他数字更接近该浮点数。

要了解差异,请考虑指数为 10001011 且有效数字为 1.11111010001111111111111 的数字。这是 8099.99951171875 的精确二进制表示。但是十进制值 8099.9995 的小数位数更少,并且比任何其他浮点数更接近这个浮点数。因此,8099.9995 是您打印出该数字时将显示的值。

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

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

最后,当您在单精度浮点中执行 8099.9995 + 0.00025 时,所涉及的数字是 8099.99951171875 和 0.0002499999827705323696136474609375 的精确二进制表示。但由于后者略大于 1/2^12,加法的结果会比 8099.99951171875 更接近 8100,所以最后会向上取整,而不是向下取整,得到 8100。

于 2013-11-25T02:52:34.950 回答
3

十进制值8099.99975有九位有效数字。这不仅仅是在浮点数中精确表示的。如果您使用CUNY 的浮点分析工具,您会看到最接近的二进制表示8099.999545FD1FFF. 当您尝试添加时,0.00025您正在遭受“失去意义”。为了不丢失较大数字的有效(左手)数字,较小的有效数字必须向右移动以匹配较大数字的比例(指数)。发生这种情况时,它的值会变为零,因为它会移出寄存器的右端。

Decimal     Exponent        Significand
---------   --------------  -------------------------
8099.9995   10001011 (+12)  1.11111010001111111111111
   0.00025  01110011 (-12)  1.00000110001001001101111

要将这些排列起来进行加法,第二个必须右移 24 位,但单精度浮点数的有效位只有 23 位。有效数字消失,留下零,因此加法无效。

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

于 2013-11-25T01:52:21.000 回答