为什么我会看到 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.pow
、45949729863572160
获得的值与 double
最接近正确的答案,因为它的精度有限(但仍然很大)。
转换为 long
没有区别,因为 Math.pow
已经将结果计算为 double
,所以答案已经差了一个。您的 long
计算值的方法是正确的。
如果您正在计算会溢出 long
的值,则可以使用 BigDecimal
, which has its own pow
method 来代替使用 double
来保留 1.0 的精度。
我必须为我大学的一个项目计算 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.pow
、45949729863572160
获得的值与 double
最接近正确的答案,因为它的精度有限(但仍然很大)。
转换为 long
没有区别,因为 Math.pow
已经将结果计算为 double
,所以答案已经差了一个。您的 long
计算值的方法是正确的。
如果您正在计算会溢出 long
的值,则可以使用 BigDecimal
, which has its own pow
method 来代替使用 double
来保留 1.0 的精度。