エンジニアの将来って?

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

【Java】BigDecimalについて解説します

こんにちは。たいら(@tairaengineer2)です。
この記事でJavaBigDecimal の解説記事です。

 

前提条件:eclipseの環境

この記事では、eclipseのバージョンは

バージョン Eclipse4.7 Oxygen

を使っています。
インストールの仕方は、下の記事をご参考ください。

www.tairax.com

BigDecimalとは

変更が不可能な、任意精度の符号付き10進数です。
BigDecimalは、任意精度のスケールなしの整数値と、32ビット整数のスケールで構成されます。
0または正の場合、スケールは小数点以下の桁数です。
負の場合、スケールなしの数値に、スケールの正負を逆にした値を指数とする10の累乗を乗算します。
つまり、BigDecimalで表される数値は(unscaledValue×10-scale)です。

BigDecimal (Java Platform SE 8)から引用させて頂きました

BigDecimalは、double型int型と同じく数字を扱う時に使います。
使う時は

import java.math.BigDecimal;

をimportして、使います。

BigDecimal使う時はかなり限られます
それは、数値計算はdouble型、float型どちらかを使えば賄えるからです。

では、BigDecimalを使う時はどんな時でしょうか?
次章で説明します。

BigDecimalが使われる場面

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

たとえば、1÷3のような無限小数の場合、double型で計算すると誤差が生まれます。
それはdouble型が勝手に丸めたり、桁数を区切ってしまうからです。
が、BigDecimalで計算するとあなたが

  • 小数点以下の桁数
  • 丸め方

を指定できるので、誤差が生まれないというわけです。

BigDecimalの生成の仕方

BigDecimalを生成するとき、valueOfメソッドを使います。
valueOfメソッドには引数の数と、引数の型で3パターンあります。
パターンは

  1. 引数が1つでdouble型
  2. 引数が1つでlong型
  3. 引数が2つでlong型とint型

それぞれ解説します。

パターンその1:引数が1つでdouble型

public static BigDecimal valueOf(double val)
Double.toString(double)メソッドで提供されるdoubleの正規の文字列表現を使用して、doubleをBigDecimalに変換します。

BigDecimal (Java Platform SE 8)から引用させて頂きました

使い方は

BigDecimal sample = BigDecimal.valueOf(0.008);

というように使います。

パターンその2:引数が1つでlong型

public static BigDecimal valueOf(long val)
long値をスケールが0のBigDecimalに変換します。この「staticファクトリ・メソッド」は、よく使われるBigDecimal値の再利用を可能にするため、(long)コンストラクタよりも優先して提供されます。

BigDecimal (Java Platform SE 8)から引用させて頂きました

使い方は

BigDecimal sample = BigDecimal.valueOf(4533333688588422888L);

というように使います。

パターンその3:引数が2つでlong型とint型

public static BigDecimal valueOf(long unscaledVal,int scale)
longのスケールなしの値とintのスケールをBigDecimalに変換します。この「staticファクトリ・メソッド」は、よく使われるBigDecimal値の再利用を可能にするため、(long, int)コンストラクタよりも優先して提供されます。

BigDecimal (Java Platform SE 8)から引用させて頂きました

使い方は

BigDecimal sample = BigDecimal.valueOf(4533333688588422888L, 1000);

というように使います。

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で定義されています。
丸め方を指定する場合

import java.math.RoundingMode;

をimportする必要があります。
RoundingModeは下記の種類があります。

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

計算と丸め方を解説しました。
次章で使い方を解説します。

 

スポンサーリンク

 

BigDecimalで色々な丸め方を使ってみる

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

  • RoundingMode.UNNECESSARYでエラーを起こさせる
  • RoundingMode.UNNECESSARYを正常終了させる

の上記2パターンでやってみます

パターンその1:RoundingMode.UNNECESSARYでエラーを起こさせるJavaサンプルコード

package bigDecimalSample;

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

/**
 * RoundingModeを練習するクラス
 * ※RoundingMode.UNNECESSARYでエラー
 *
 */
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" 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
java.lang.ArithmeticException: Rounding necessary
	at java.math.BigDecimal.commonNeedIncrement(BigDecimal.java:4148)
	at java.math.BigDecimal.needIncrement(BigDecimal.java:4204)
	at java.math.BigDecimal.divideAndRound(BigDecimal.java:4112)
	at java.math.BigDecimal.divide(BigDecimal.java:5183)
	at java.math.BigDecimal.divide(BigDecimal.java:1561)
	at java.math.BigDecimal.divide(BigDecimal.java:1591)
	at bigDecimalSample.RoundingModeSample.main(RoundingModeSample.java:34)

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

パターンその2:RoundingMode.UNNECESSARYを正常終了させるJavaサンプルコード

パターン2では、RoundingMode.UNNECESSARYは割り切れるようにして計算します。

package bigDecimalSample;

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

/**
 * RoundingModeを練習するクラス
 * ※RoundingMode.UNNECESSARYは正常終了
 *
 */
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));
        // RoundingMode.UNNECESSARYを正常終了するように計算する
        System.out.println("RoundingMode.UNNECESSARY:"
                + cal1.divide(cal1, 5, 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についての解説記事です。
この記事が皆さんのご参考になれば幸いです。

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

 

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

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

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

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