エンジニアの将来って?

現在7年目のCOBOL→Java→C#エンジニアが、将来への考えや勉強のアウトプット、たまに腎臓について書くブログです

【Java】BigDecimalの使い方を解説、double型と比較もします

こんにちは。たいら(@tairaengineer2)です。
今回の記事はJavaのアウトプット記事です。
この記事でBigDecimalの計算の仕方、使われる場面、そしてdouble型との比較もします。

BigDecimalの計算の仕方

BigDecimalではdouble型やint型のように「-」「+」の演算子を使いません。
メソッドで計算します。

演算 メソッド 使用例 意味
足し算 add a.add(b) a + b
引き算 subtract a.subtract(b) a - b
掛け算 multiply a.multiplyt(b) a * b
割り算 divide a.divide(b, 3, RoundingMode.HALF_UP)
a.divide(b)
a / b

割り算だけ違いますね。

a.divide(b, 3, RoundingMode.HALF_UP)

引数が3つあるdivideメソッドは以下のような意味です

引数 意味
1つ目の引数 割る数
2つ目の引数 小数点第何位までで丸めるのか
3つ目の引数 どのように丸めるのか
切り捨てなのか
四捨五入するのか

を指定しています。
これで無限小数にならないようしています。

もちろん引数が1つのdivideメソッドでも計算することができますが、無限小数になった場合

Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
    at java.math.BigDecimal.divide(BigDecimal.java:1514)
    at bigDecimalSample.BigDecimalSample.main(BigDecimalSample.java:30)>

というようにArithmeticException例外がスローされます。
無限小数になるときの丸め方はどのように設定するのか見ていきましょう。

BigDecimalの丸め方

丸め方を指定するRoundingModeenumで定義されています。
RoundingModeは下記の種類があります。

丸めモード 丸め方
RoundingMode.CEILING 切り上げ
RoundingMode.DOWN 0に近づくように丸める
RoundingMode.FLOOR 切り捨て
RoundingMode.HALF_DOWN 「もっとも近い数字」に丸める
(両隣りの数字が等距離の場合は切り捨て)
RoundingMode.HALF_EVEN 「もっとも近い数字」に丸める
(両隣りの数字が等距離の場合は偶数側に丸める)
RoundingMode.HALF_UP 四捨五入
RoundingMode.UNNECESSARY 丸めが必要でないことを表す丸めモード
RoundingMode.UP 0から離れるように丸める

どんな表示になるか実験してみます。
1÷0.7をして色んなRoundingModeで丸めてみます。

Javaプログラム

package bigDecimalSample;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * RoundingModeを練習するクラス
 *
 */
public class RoundingModeSample {
    public static void main(String[] args) {
        BigDecimal cal1 = BigDecimal.ONE;
        BigDecimal cal2 = BigDecimal.valueOf(0.7);

        // 色んなRoundingModeを表示
        System.out.println("計算結果 :1.428571428571429…");
        System.out.println("RoundingMode.UP :"
                + cal1.divide(cal2, 5, RoundingMode.UP));
        System.out.println("RoundingMode.DOWN :"
                + cal1.divide(cal2, 5, RoundingMode.DOWN));
        System.out.println("RoundingMode.HALF_UP :"
                + cal1.divide(cal2, 5, RoundingMode.HALF_UP));
        System.out.println("RoundingMode.HALF_DOWN :"
                + cal1.divide(cal2, 5, RoundingMode.HALF_DOWN));
        System.out.println("RoundingMode.HALF_EVEN :"
                + cal1.divide(cal2, 5, RoundingMode.HALF_EVEN));
        System.out.println("RoundingMode.CEILING :"
                + cal1.divide(cal2, 5, RoundingMode.CEILING));
        System.out.println("RoundingMode.FLOOR :"
                + cal1.divide(cal2, 5, RoundingMode.FLOOR));
        System.out.println("RoundingMode.UNNECESSARY:"
                + cal1.divide(cal2, 5, RoundingMode.UNNECESSARY));
    }
}

実行結果

計算結果                :1.428571428571429…
Exception in thread "main" java.lang.ArithmeticException: Rounding necessary
    at java.math.BigDecimal.divide(BigDecimal.java:1346)
    at java.math.BigDecimal.divide(BigDecimal.java:1413)
    at bigDecimalSample.RoundingModeSample.main(RoundingModeSample.java:24)
RoundingMode.UP         :1.42858
RoundingMode.DOWN       :1.42857
RoundingMode.HALF_UP    :1.42857
RoundingMode.HALF_DOWN  :1.42857
RoundingMode.HALF_EVEN  :1.42857
RoundingMode.CEILING     :1.42858
RoundingMode.FLOOR    :1.42857

RoundingMode.UNNECESSARYは無限小数になった場合、 ArithmeticException例外 がスローされました。
丸めモードが必要ないって指定しているのに、答えが無限小数だからですね。

今度はRoundingMode.UNNECESSARYは割り切れるようにして、再計算してみます。

計算結果                :1.428571428571429…
RoundingMode.UP         :1.42858
RoundingMode.DOWN       :1.42857
RoundingMode.HALF_UP    :1.42857
RoundingMode.HALF_DOWN  :1.42857
RoundingMode.HALF_EVEN  :1.42857
RoundingMode.CEILING    :1.42858
RoundingMode.FLOOR     :1.42857
RoundingMode.UNNECESSARY:1.00000

無事計算結果が出力されました!
RoundingModeでは、

  • RoundingMode.CEILING
  • RoundingMode.DOWN
  • RoundingMode.HALF_UP

を覚えておけば大抵の計算は賄えそうですかね。

BigDecimalが使われる場面

数字を扱うとき、通常の計算ならばint型やdouble型で十分です。
BigDecimal型を使うときは、正確に計算する必要があるときです。
たとえば銀行の金額計算など、誤差が許されないときによく使われます。
では、どんな時に誤差が出るのでしょうか?
実験してみましょう。

BigDecimal型とdouble型を比較

double型とBigDecimal型で同じ計算をして比較します。
どちらも1と0.9に対して、足したり引いたりかけたり割ったりしてます。

Javaプログラム

package bigDecimalSample;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * double型とBigDecimal型の違いを確認するクラス
 *
 */
public class BigDecimalSample {
    public static void main(String[] args) {
        // double型の変数を生成
        double doubleCal1 = 1;
        double doubleCal2 = 0.9;

        // BigDecimal型の変数を生成
        BigDecimal bigDecimalCal1 = BigDecimal.ONE;
        BigDecimal bigDecimalCal2 = BigDecimal.valueOf(0.9);

        // double型とBigDecimal型の計算結果の違いを確認
        double doubleAdd = doubleCal1 + doubleCal2;
        double doubleSub = doubleCal1 - doubleCal2;
        double doubleMul = doubleCal1 * doubleCal2;
        double doubleDev = doubleCal1 / doubleCal2;

        BigDecimal bigDecimalAdd = bigDecimalCal1.add(bigDecimalCal2);
        BigDecimal bigDecimalSub = bigDecimalCal1.subtract(bigDecimalCal2);
        BigDecimal bigDecimalMul = bigDecimalCal1.multiply(bigDecimalCal2);
        BigDecimal bigDecimalDev = bigDecimalCal1.divide(bigDecimalCal2, 3, RoundingMode.HALF_UP);

        System.out.println("----------足し算----------");
        System.out.println("double型  :" + doubleAdd);
        System.out.println("BigDecimal型:" + bigDecimalAdd);

        System.out.println("----------引き算----------");
        System.out.println("double型  :" + doubleSub);
        System.out.println("BigDecimal型:" + bigDecimalSub);

        System.out.println("----------掛け算----------");
        System.out.println("double型  :" + doubleMul);
        System.out.println("BigDecimal型:" + bigDecimalMul);

        System.out.println("----------割り算----------");
        System.out.println("double型  :" + doubleDev);
        System.out.println("BigDecimal型:" + bigDecimalDev);

    }
}

実行結果

----------足し算----------
double型  :1.9
BigDecimal型:1.9
----------引き算---------- 
double型  :0.09999999999999998
BigDecimal型:0.1
----------掛け算----------
double型  :0.9
BigDecimal型:0.9
----------割り算----------
double型  :1.1111111111111112
BigDecimal型:1.111

どうでしょう。足し算と掛け算に差異はありませんでした。
しかし、引き算と割り算には差異があります。
特に引き算。なんで0.1にならなかったんだ… という方はこれをご参考に

qiita.com

このように同じ計算でも場合によっては結果が異なることを誤差と言います。
誤差が生まれるケースとしては、割り算で答えが無限小数になる場合などです。
この誤差が許されないときに、BigDecimalはよく使われます。

まとめ

BigDecimalの仕様についてだいたいは振り返れたかなと思います。
いかがだったでしょうか?
この記事が皆さんのご参考になれば幸いです。

ではでは~(・ω・)ノシ

参考にさせて頂いたサイト様

RoundingMode (Java Platform SE 8)

BigDecimalの使い方 | Java好き

 

ほかにも勉強記事を書いてます。
よければご参考ください。

【Java】標準入力から判定して、じゃんけんゲームをするプログラム

【Java8】filterメソッドを解説【ラムダ式】

【Java】getResourceAsStreamメソッドでプロパティファイルを読み込む