Math.pow(2,63) - 1 == Math.pow(2,63) - 512 是真的。

3

我认为这很有趣:

System.out.println(  (long)(Math.pow(2,63) - 1) == Long.MAX_VALUE); // true
System.out.println(  (long)(Math.pow(2,63) - 5) == Long.MAX_VALUE); // true 
System.out.println(  (long)(Math.pow(2,63) - 512) == Long.MAX_VALUE); // true
System.out.println(  (long)(Math.pow(2,63) - 513) == Long.MAX_VALUE); // false

最后三行应该打印false,但实际上只有最后一个,在其中我减去了513给出了正确的答案。
为什么512/513是不准确的分界点?我能想到的唯一联系就是512是千字节的一半。
我知道这个问题的原因是double的不准确性,这源于Math.pow(int, int)返回一个double
我尝试过使用java.math.BigInteger,这是我的结果:
BigInteger b = new BigInteger("2");

System.out.println(  (b.pow(63).longValue() - 1) == Long.MAX_VALUE  ); // true
System.out.println(  (b.pow(63).longValue() - 5) == Long.MAX_VALUE  ); // false
System.out.println(  (b.pow(63).longValue() - 512) == Long.MAX_VALUE  ); // false
System.out.println(  (b.pow(63).longValue() - 513) == Long.MAX_VALUE  ); // false

2
更简单的例子:double d = Long.MAX_VALUE; System.out.println(d-512 == d); System.out.println(d-513 == d); - assylias
2个回答

8

这是一个浮点数舍入问题。双精度浮点数有效的尾数位数为53位,因此在2^63范围内,相邻的双精度浮点数之间相差1024。512是您可以减去并且仍然会向上取整的最大值。减去513已经超过了下一个双精度浮点数的一半,所以结果向下取整。

这个程序演示了这个问题:

import java.math.BigDecimal;

public class Test
{
  public static void main(String[] args) {
    double maxValue = (double)Long.MAX_VALUE;
    System.out.println(new BigDecimal(maxValue));
    double nextDown = Math.nextAfter(maxValue, 0);
    System.out.println(new BigDecimal(nextDown));
    System.out.println(maxValue-nextDown);
  }
}

它输出:
9223372036854775808
9223372036854774784
1024.0

显示Long.MAX_VALUE的双倍与零方向上下一个双精度浮点数之间的1024间隙。

2

double 的内部表示使用 52 位来存储有效数字。这意味着任何由 double 表示的数字都具有以下形式:

1.ddddddddddddddddddddddd x 2 ^ n
  ^^ 52 zeroes or ones ^^

当你将另一种格式的数字转换为double类型时,你会得到最接近这个格式的数字。

现在,数字2 ^ 63 = 9223372036854775808有一个精确的double表示,因为它可以被存储为

1.00000000000000000000000 x 2 ^ 63
  ^^ 52 zeroes         ^^

数字2 ^ 63 - 2 ^ 10 = 9223372036854774784也有一个精确的double表示。它是

1.11111111111111111111111 x 2 ^ 62
  ^^ 52 ones           ^^

显然,这两个数之间没有一个能够被double准确表示的数字。
现在,
  • Long.MAX_VALUE = 2 ^ 63 - 1 = 9223372036854775807。最接近这个数且可以被double准确表示的是2 ^ 63 = 9223372036854775808
  • 2 ^ 63 - 5 = 9223372036854775803。同样,最接近这个数且可以被double准确表示的是2 ^ 63 = 9223372036854775808
  • 2 ^ 63 - 512 = 9223372036854775296。这个数恰好位于92233720368547758089223372036854774784之间。因此,Java必须选择这两个值中的一个来表示9223372036854775296。它会向上舍入,也就是选择更大的那个数。
  • 2 ^ 63 - 513 = 9223372036854775295。最接近这个数且可以被double准确表示的是2 ^ 63 - 2 ^ 10 = 9223372036854774784。最后,我们得到了一个与Long.MAX_VALUE不同double表示。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接