为什么计数到2 ^ 24会很快执行,但计算到2 ^ 25需要更长的时间?

时间:2017-01-12 16:53:52

标签: java floating-point

我正在摆弄无限循环来测试其他代码/我的理解,并遇到了这种奇怪的行为。在下面的程序中,从0到2 ^ 24的计数在我的机器上需要<100ms,但是计数到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;
            }
        }
    }
}

1 个答案:

答案 0 :(得分:16)

这是因为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