🐥
【Java】Math.pow関数で発生する誤差
概要
AtCoderRegularContest106のA問題、A-106は累乗の結果を判定する問題です。私は今はAtCoderをJavaで解いているので、Javaで累乗ならMath.pow関数だろうということで、Math.pow関数を使ったら見事に誤差が発生しハマりました。ということで、今回はMath.pow関数の誤差に関することを書いてみたいと思います。
Math.pow関数とは
powメソッドの記事にあるとおり、基数と指数をdouble型で引数にとって累乗した結果を返す関数です。double型と言いつつ今回のAtCoderの問題では、小数ではなく整数を入れたので、誤差発生しないだろうという甘い考えで使ったら誤差が発生したわけです。
どのくらいで誤算が発生するのか
まずは5の累乗で、Math.pow関数を使った結果と愚直に累乗した結果を比較しました。そうすると5の25乗あたりから、Math.pow関数で計算した結果が想定と異なるものになっていました。
なお、実行環境は,AdoptOpenJDK (build 11.0.8+10)
で確認してます。
計算内容 | Math.pow関数の結果 | 愚直に累乗した結果 |
---|---|---|
5の10乗 | 9765625 | 9765625 |
5の20乗 | 95367431640625 | 95367431640625 |
5の25乗 | 298023223876953152 | 298023223876953125 |
3の累乗で、もう少し誤差が発生するタイミングを確認してみました。すると3の34乗から誤算が発生するのを確認しました。
計算内容 | Math.pow関数の結果 | 愚直に累乗した結果 |
---|---|---|
3の10乗 | 59049 | 59049 |
3の20乗 | 3486784401 | 3486784401 |
3の30乗 | 205891132094649 | 205891132094649 |
3の33乗 | 5559060566555523 | 5559060566555523 |
3の34乗 | 16677181699666568 | 16677181699666569 |
3の35乗 | 50031545098999704 | 50031545098999707 |
結論としては、10の16乗くらいの結果になると、Math.pow関数では誤差が生じると考えられます。ので、計算結果が大きな数になる見込みであれば、Math.pow関数は使わずに計算していくのが確実です。愚直に計算をしていくか、BigInteger型のpow関数を使う方法も良いと思います。
今回の結果を確認したコード
public class PowSample {
public static void main(String[] args){
System.out.println("【5の10乗】");
displayRuijou(5, 10);
System.out.println("【5の20乗】");
displayRuijou(5, 20);
System.out.println("【5の25乗】");
displayRuijou(5, 25);
System.out.println("【3の10乗】");
displayRuijou(3, 10);
System.out.println("【3の20乗】");
displayRuijou(3, 20);
System.out.println("【3の30乗】");
displayRuijou(3, 30);
System.out.println("【3の33乗】");
displayRuijou(3, 33);
System.out.println("【3の34乗】");
displayRuijou(3, 34);
System.out.println("【3の35乗】");
displayRuijou(3, 35);
}
private static void displayRuijou(long kisuu, int shisuu) {
// Math.powを使って累乗
double powDouble = Math.pow(kisuu, shisuu);
System.out.println((long)powDouble);
// 愚直に書いて累乗
long result = 1;
for(int i = 0; i < shisuu; i++) {
result= result * kisuu;
}
System.out.println(result);
}
}
Discussion