【Java】Math.pow関数で発生する誤差

2 min読了の目安(約2400字TECH技術記事

概要

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);
    }
}