我摆弄无限循环来测试其他代码/我的理解,并发现了这种奇怪的行为。在下面的程序中,从0到2 ^ 24的计数在我的计算机上花费的时间不到100毫秒,但是到2 ^ 25的计数则花费了几个数量级的时间(在编写本文时,它仍在执行)。

为什么会这样呢?

该版本使用Java 1.8.0_101,位于Windows 10的64位副本上。

TestClass.java

public class TestClass {
    public static void main(String[] args) {
        addFloats((float) Math.pow(2.0, 24.0));
        addFloats((float) Math.pow(2.0, 25.0));
    }

    private static void addFloats(float number) {
        float f = 0.0f;
        long startTime = System.currentTimeMillis();

        while(true) {
            f += 1.0f;
            if (f >= number) {
                System.out.println(f);
                System.out.println(number + " took " + (System.currentTimeMillis() - startTime) + " msecs");
                break;
            }
        }
    }
}

最佳答案

这是因为float具有可以表示的最低精度,随着float的值变大,精度会降低。在2 ^ 24到2 ^ 25之间的某个地方,加一个不再足以将值更改为下一个可表示的最大数字。那时,每次循环,f都会保持相同的值,因为f += 1.0f不再更改它。

如果将循环更改为此:

while(true) {
    float newF = f + 1.0f;
    if(newF == f) System.out.println(newF);
    f += 1.0f;
    if (f >= number) {
        System.out.println(f);
        System.out.println(number + " took " + (System.currentTimeMillis() - startTime) + " msecs");
        break;
    }
}

您可以看到这种情况。好像f达到2 ^ 24时它就停止增加了。

如果使用2 ^ 25运行,则以上代码的输出将是一个无穷多个“1.6777216E7”。

您可以使用 Math.nextAfter function测试该值,它告诉您下一个可表示的值。如果您尝试运行此代码:
float value = (float)Math.pow(2.0, 24.0);
System.out.println(Math.nextAfter(value, Float.MAX_VALUE) - value);

您会看到2 ^ 24之后的下一个可表示值是2 ^ 24 + 2。

要详细了解这种情况的发生原因以及它在哪里发生的重要性,请参阅this answer

07-24 09:39