代码
public class test{
public static void main(String[] args){
double first = 3.14 ;
first++;
System.out.println(first);
}
}
结果
ubuntu@john:~/Desktop$ javac test.java
ubuntu@john:~/Desktop$ java test
输出:4.140000000000001
对于几乎所有其他案例,我都得到了预期的答案......
例如:对于 4.14,结果是 5.14...
为什么这个案例很特别?
有很多数字可以精确地用十进制表示,但不能精确地用二进制表示。也就是说,它们具有终止十进制表示,但没有终止二进制表示。
要理解这一点,请考虑数字 1/3。它没有终止十进制表示法——我们可以继续写 0.3333333333333 一段时间,但迟早我们必须停下来,而且我们还没有写完 1/3。
当我们尝试用二进制写 2.14 时,同样的事情也会发生。它是 10.001000111... 以及最终开始重复的更多 0 和 1,就像 0.333333 以十进制重复一样。
现在 double 只是一个有 53 位有效数字的二进制数。所以它不能准确地存储 2.14,但它可以非常接近。现在看看当我们开始递增它时会发生什么。
2.14 = 10.001000111 .... (53 significant figures, 51 of them after the dot)
3.14 = 11.001000111 .... (53 significant figures, 51 of them after the dot)
4.14 = 100.001000111 ... (53 significant figures, 50 of them after the dot)
5.14 = 101.001000111 ... (53 significant figures, 50 of them after the dot)
所以当我们从 2.14 到 3.14 时我们没有失去任何准确性,因为点后面的部分没有改变。同样,当我们从 4.14 升级到 5.14 时。
但是当我们从 3.14 到 4.14 时,我们失去了准确性,因为我们需要在点之前多一个数字,所以我们在点之后丢失了一个数字。
现在 Java 有一个复杂的算法来确定如何显示 float 。基本上,它会选择最短的十进制表示形式,它更接近您要表示的 float ,而不是任何其他 float 。这样,如果你写 double d = 2.14;
, 然后你会得到一个非常接近 2.14 的 float ,当你打印出来时它总是显示为 2.14。
但是一旦您开始弄乱点后的数字,Java 打印算法的复杂性就会开始出现 - 最终打印的数字可能与您预期的不同。
因此,当您递增 double
时,这不会发生, 但不要更改点之前的位数。它只会在您增加 double
时发生超过 2 的幂;因为这会改变点之前的位数。
为了说明这一点,我运行了这段代码。
for(int i = 0; i < 1000000000; i++) {
if ( i + 1 + 0.14 != i + 0.14 + 1 ) {
System.out.println(i + 0.14 + 1);
}
}
得到了这个输出。
4.140000000000001
1024.1399999999999
2048.1400000000003
4096.139999999999
1048576.1400000001
2097152.1399999997
4194304.140000001
观察所有这些有差异的值刚好超过 2 的幂。