为什么我会看到 Math.pow(11, 16) 的一次性错误?

Why am I seeing a one-off error with Math.pow(11, 16)?

我必须为我大学的一个项目计算 11^16。 Math.pow(11,16) 以某种方式计算出的解决方案正好比 WolframAlpha 或我的其他计算方法少 1。

我的代码是:

public class Test {
    public static void main(String args[]) {
        long a = 11;
        long b = 16;
        System.out.println("" + (long)Math.pow(a, b));
        System.out.println("" + squareAndMultiply(a, b));
    }

    public static long squareAndMultiply(long b, long e){
        long result = 1;
        long sq = b;
        while(e>0){
            if(e%2==1){
                result *= sq;
            }
            sq = sq * sq;
            e /= 2;
        }
        return result;
    }
}

代码的结果是:

math.pow(11,16):

45949729863572160

平方乘(11,16):

45949729863572161

造成这种差异的根本原因是精度损失,因为双精度数字精确到小数点后十六位。

演示的一种方法是这个例子。

System.out.println((double)999999999999999999L);

输出:

1.0E18

Math.pow(11, 16) 的输出是 4.594972986357216E16,在转换为 long 时会转换为 45949729863572160

如果您对了解精度损失更感兴趣,可以查看this

使用 floating-point 算法时,您处于灰色区域,其中 double 的精度低于 long 的精度(即使 double 的范围 大得多)。

A double 有 53 位精度,而 long 可以将所有 64 位用于精度。当您处理高达 1116 的值时,一个 double 值与下一个值之间的差异变得很明显。

Java 有一个 built-in method Math.ulp (“最后一个单位”),它有效地给出了连续可表示值之间的值差异。 (有一个 double 版本和一个 float 版本。)

System.out.println(Math.ulp(Math.pow(11, 16)));

8.0

这意味着大于 45949729863572160 的最小可能 double 值是 45949729863572168

长值 45949729863572161 是正确的,但是您使用 Math.pow45949729863572160 获得的值与 double 最接近正确的答案,因为它的精度有限(但仍然很大)。

转换为 long 没有区别,因为 Math.pow 已经将结果计算为 double,所以答案已经差了一个。您的 long 计算值的方法是正确的。

如果您正在计算会溢出 long 的值,则可以使用 BigDecimal, which has its own pow method 来代替使用 double 来保留 1.0 的精度。