我摆弄无限循环来测试其他代码/我的理解,并发现了这种奇怪的行为。在下面的程序中,从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