BigDecimalの使い方

生成、演算、丸め、比較、出力などの基本的なBigDecimalの使い方のまとめ。

執筆時バージョン
Java

Java SE 8

floatやdoubleでは正確な計算(精度を指定した計算)ができない。そもそも正確な計算用ではない。なぜdouble/floatではなくBigDecimalを使うのかの理由についてははこちらをご覧下さい。

概要

Javaでは精度を指定した計算を行うためにBigDecimalが用意されている。BigDecimalではそれぞれの計算をメソッドで行い、精度や丸め方法を指定することができる。

BigDecimalクラス
Figure 1. BigDecimalクラス

BigDecimalはNumber型を継承しているので、LongやDoubleと同様にNumber型でもある。イミュータブルなので、各計算メソッドは計算結果をBigDecimalで返す設計になっている。

本体のBigDecimal以外にも「RoundingMode」「MathContext」を利用する。「RoundingMode」は丸め方法を表現するEnum。「四捨五入」や「切り捨て」などの基本的なものは用意されている。「MathContext」は精度と「RoundingMode」をひとまとめにしたモノ。

BigDecimalは内部的には次のような形式で値を保持している。

unscaledValue×10 -scale

このように整数と10のマイナス乗を利用して表現するので、doubleやfloatみたいに値を作成しただけで誤差が出るようなことはない。

スケールと精度

Table 1. スケールと精度の説明
用語 説明

スケール

小数点以下の桁数

精度

スケールなしの値の桁数

BigDecimalはスケールと精度を保持するので、どちらを中心とした計算も可能。例えば金額計算ではスケールが重要なので、精度を細かく指定することが無かったりする。科学系の計算の時はその逆で精度を細かく指定して、スケールをあまり気にしないことがある。

スケールは金額計算のときに、よく使う。基本的に除算(割り算)時の小数何桁まで有効なのかを意識していればいい。加算、減算、乗算にはスケールを指定できるメソッドはない。 これらの場合のスケールは自動で決定されるのでそれに従えば特に問題は無く、計算の最後の方で丸めを行えばいい。

精度は科学系の計算のときに、よく使う。精度を計算に指定する場合は、精度と丸めモードをひとまとめにしたMathContextを使う。ほとんどの計算メソッドでMathContextは指定可能。

やってはいけない

途中でdouble/floatにしてしまう

実際に計算を行っているところのみをBigDecimalするだけでは正確な計算ができない。doubleやfloatは値をメモリに乗せただけで誤差が生じてしまう。

// ダメな例「0.09999999999999998」と出力される
// 途中でdouble型に落としてしまっている
BigDecimal temp = new BigDecimal("0.1").add(new BigDecimal("0.2"));
double badResult = temp.doubleValue() - 0.2d;
System.out.println(String.valueOf(badResult));

正確な値を保持し続けようとする場合、個々の計算だけではなくプログラム全体、はたまたシステム全体の入出力までを包括的に考慮しないと意味が無い。

まずはプログラム全体でBigDecimalにしているかに注意する。BigDecimalで計算をしていても、メソッドの引数や戻り値やフィールドに保持する型などでdouble/floatにしてしまえば、精度を保証できない。

またプログラム外でもDBにおける入出力、WebなどのUIにおける入出力などに注意する。DBにおいても保存する項目の型、UIでも精度をロスしないで値を受け取れるかなど、全体を通して気を使っていないと精度を保証できない。

生成

BigDecimalのコンストラクタとファクトリは合計19種類あるが、次の順に考えると精度を落としにくくなる。

  1. 定数(0,1,10のみ)

  2. int,long,BigIntegerの整数型を元にする

    1. BigDecimal.valueOf(long val),BigDecimal.valueOf(long unscaledVal, int scale)

    2. new BigDecimal(int val)など

  3. Stringやchar[]の文字列(数字)を元にする

    1. new BigDecimal(String val)など

  4. double(float)を元にする

    1. new BigDecimal(double val, MathContext mc)

    2. BigDecimal.valueOf(double val)

    3. new BigDecimal(double val)

まずは何を元にして作るかが重要なので、上から順に検討する。定数と整数型を元にする場合は精度が落ちることはない。文字列(数字)も完璧とはいかないが精度は落ちにくい。この中で誤差を覚悟しなくてはいけないのはdouble(float)を元にする場合である。double型に乗っけている時点で丸まっている可能性があるので、それを元にBigDecimalにしたとしても精度が落ちる可能性がある。

valueOf()はインスタンスの再利用をしてくれるので、同引数のコンストラクタより優先する。

整数型からスタートできるかどうかは仕様によるので、小数を受け取るにしてもなんとか文字列から生成したい。というか精度が落ちないようにプログラム内はBigDecimalで受け渡し続けるなどの全般を考慮した設計や仕様が必要。どうしてもdouble(float)でしか受け取れない場合は、誤差を覚悟して生成する。

定数

0,1,10はよく使うためフィールドに定数で用意されている。

Table 2. BigDecimalの定数
フィールド

0

BigDecimal.ZERO

1

BigDecimal.ONE

10

BigDecimal.TEN

文字列(数字)による生成

不慣れだとピンとこないかもしれないが、文字列(数字)はdoubleよりも正確に小数点以下の情報を表現できる。例えば”0.190”であれば、次のようになる。

// 出力: 0.190
System.out.println(new BigDecimal("0.190"));

// 出力: 0.190000000000000002220446049250313080847263336181640625
System.out.println(new BigDecimal(0.190d));

整数に比べれば限界はあるが、たいていの数値はロス無く表現できる。

演算

BigDecimalではメソッドを使って計算する。メソッドの戻り値が演算結果。

Table 3. 演算のメソッド
演算 対応するメソッド

加算

add(BigDecimal augend)
add(BigDecimal augend, MathContext mc)

減算

subtract(BigDecimal subtrahend)
subtract(BigDecimal subtrahend, MathContext mc)

乗算

multiply(BigDecimal multiplicand)
multiply(BigDecimal multiplicand, MathContext mc)

除算(割り算)

divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
divide(BigDecimal divisor, MathContext mc)

商の余り

remainder(BigDecimal divisor)
remainder(BigDecimal divisor, MathContext mc)

べき乗

pow(int n)
pow(int n, MathContext mc)

除算(割り算)

除算では必ず丸めモードを指定するようにする。無指定の場合、計算結果が無限小数になるとArithmeticExceptionがスローされる。もちろん0除算もダメ。

BigDecimal six = BigDecimal.valueOf(6);
BigDecimal ten = BigDecimal.TEN;
BigDecimal result = ten.divide(six, 2, RoundingMode.HALF_UP);
// 出力: 1.67
System.out.println(result);

丸め

丸めを行うためのメソッドは2種類ある。スケールで丸めるのか精度で丸めるかによって選ぶ。計算の最後にスケールや桁数を整えるのに利用する。

Table 4. 丸めのメソッド
丸め 対応するメソッド

スケールによる丸め

setScale(int newScale, RoundingMode roundingMode)

精度による丸め

round(MathContext mc)

// スケールが2なので、スケール2までが保証され、それより後ろで丸めが行われる。
// 出力: 12.35
BigDecimal value = new BigDecimal("12.345");
BigDecimal result = value.setScale(2, RoundingMode.HALF_UP);
System.out.println(result);
// 精度4桁なので、「12.34」までが保証され、それより後ろで丸めが行われる。
// 出力: 12.35
BigDecimal value = new BigDecimal("12.345");
MathContext mc = new MathContext(4, RoundingMode.HALF_UP);
BigDecimal result = value.round(mc);
System.out.println(result);

RoundingMode

RoundingModeは、丸めモードを表すenumで一般的な丸めは網羅している。BigDecimalの定数でも丸めモードが定義されているが、J2SE5以前のためのものなのでこちらを使う。

Table 5. RoundingModeの一覧
丸めモード 一般的な名称

RoundingMode.UP

切り上げ

RoundingMode.DOWN

切り捨て

RoundingMode.HALF_UP

四捨五入

RoundingMode.HALF_DOWN

「もっとも近い数字」に丸める

RoundingMode.HALF_EVEN

銀行家の丸め、JIS丸め

RoundingMode.CEILING

切り上げ

RoundingMode.FLOOR

切り捨て

RoundingMode.UNNECESSARY

丸めが必要でないことを表す丸めモード

RoundingMode.UNNECESSARYは使わない。このモードが指定されているときに無限小数になるとArithmeticExceptionになる。丸めモードが指定されなかった時用に設定される値。

値の比較

equals()はスケールまで一致しないといけないので、基本的にはcompareTo()を使う。

Table 6. 比較のメソッド
メソッド 概要

equals(Object x)

値とスケールが等しい場合true。

compareTo(BigDecimal val)

値が等しい場合true(スケールは異なっていても)。

BigDecimal aValue = new BigDecimal("0.19");
BigDecimal bValue = new BigDecimal("0.190");

// 出力:false (精度が違う)
System.out.println(aValue.equals(bValue));

//  出力:true (値は同じと判断される)
System.out.println(aValue.compareTo(bValue) == 0);

出力

BigDecimalでは文字列で出力するメソッドとして、次の3種類が用意されている。これらが利用できなければ、NumberFormatを使う。

Table 7. 出力メソッド
出力メソッド 出力の特徴

toPlainString()

Eの表記を使わずに、表現する。

toString()

○.○○○E○を目指す。整数が1桁になるように調整される。

toEngineeringString()

○○○.○○○E○を目指す。整数が3桁になるように調整される。

Table 8. 出力例
toPlainString() toString() toEngineeringString()

0

0

0

0

0.0

0.0

0.0

0.0

0.000001

0.000001

0.000001

0.000001

0.0000001

0.0000001

1E-7

100E-9

0.0000001234

0.0000001234

1.234E-7

123.4E-9

Appendix B: 改訂履歴

  • v1.0, 2013-08-27: 初稿