- JAVA
- 技術 💻
Java BigDecimalの使い方|金額計算の丸めと比較の注意点
Javaで金額や数量など、1円・1単位の誤差も許されない計算をするときは、doubleではなくBigDecimalを使います。doubleは内部で2進数を使うため、0.1のような10進数の小数を正確に表せず、計算を重ねると誤差が生じるからです。
ただし、BigDecimalは使い方を間違えると、誤差がそのまま入り込んだり、例外で止まったりします。この記事では、doubleで誤差が出る理由から、BigDecimalの正しい生成方法、四則演算、丸め、比較の注意点までを解説します。
doubleで金額計算をすると誤差が出る理由
System.out.println(0.1 + 0.2); // 0.30000000000000004
コンピュータは数値を2進数で持ちます。0.1は2進数では無限に続く小数になるため、doubleの限られた桁数で途中までしか保存できません。この小さなずれが、計算を重ねると表面に出てきます。
1回の計算では気づかないほどの誤差でも、大量の明細を集計すると合計が1円ずれる、比較(==)が意図どおりにならない、といった形で問題になります。金額計算でdoubleを使うのは避けるのが原則です。
BigDecimalの生成:文字列かvalueOfを使う
最も重要な注意点が、生成の方法です。
new BigDecimal(0.1); // × 0.1000000000000000055511151231257827021181583404541015625
new BigDecimal("0.1"); // ○ 0.1
BigDecimal.valueOf(0.1); // ○ 0.1
new BigDecimal(0.1) は、doubleがすでに持っている誤差ごと、正確にBigDecimalに変換します。つまり、誤差を避けるためにBigDecimalを使ったのに、生成の時点で誤差を取り込んでしまうのです。
- 文字列から生成する(
new BigDecimal("0.1"))— 最も確実 - doubleしかない場合は
BigDecimal.valueOf(double)— doubleを文字列表現に変換してから生成するため、見た目どおりの値になる - 整数なら
BigDecimal.valueOf(long)やBigDecimal.ONEなどの定数を使う
四則演算:結果は新しいオブジェクトで返る
BigDecimalは不変(イミュータブル)です。計算メソッドは、元の値を変えずに新しいBigDecimalを返します。
BigDecimal price = new BigDecimal("1000");
price.add(new BigDecimal("100")); // × 結果を受け取っていないので、priceは1000のまま
price = price.add(new BigDecimal("100")); // ○ 1100
| 演算 | メソッド |
|---|---|
| 足し算 | add |
| 引き算 | subtract |
| 掛け算 | multiply |
| 割り算 | divide |
割り算は、丸め方を必ず指定する
BigDecimal one = BigDecimal.ONE;
BigDecimal three = new BigDecimal("3");
one.divide(three); // ArithmeticException: Non-terminating decimal expansion
1÷3のように割り切れない計算で、桁数と丸め方を指定しないと、BigDecimalは正確な答えを表せないため例外を投げます。割り算では、小数点以下の桁数と丸めモードを必ず指定します。
one.divide(three, 2, RoundingMode.HALF_UP); // 0.33
丸め:setScaleとRoundingMode
金額計算の丸めは、setScale(桁数, 丸めモード) で行います。
BigDecimal amount = new BigDecimal("123.456");
amount.setScale(2, RoundingMode.HALF_UP); // 123.46
amount.setScale(0, RoundingMode.DOWN); // 123
主な丸めモード
| RoundingMode | 動き | 2.5 → | 3.5 → | -2.5 → |
|---|---|---|---|---|
HALF_UP |
四捨五入 | 3 | 4 | -3 |
HALF_EVEN |
偶数丸め(銀行丸め) | 2 | 4 | -2 |
HALF_DOWN |
五捨六入 | 2 | 3 | -2 |
DOWN |
0に向かって切り捨て | 2 | 3 | -2 |
FLOOR |
小さい方へ切り捨て | 2 | 3 | -3 |
UP |
0から離れる方へ切り上げ | 3 | 4 | -3 |
CEILING |
大きい方へ切り上げ | 3 | 4 | -2 |
HALF_EVEN(偶数丸め)がある理由
ちょうど0.5のときに常に切り上げると、大量のデータを丸めて集計したとき、合計が少しずつ大きい方に偏ります。HALF_EVENは、0.5のときに偶数の側に丸めることで、切り上げと切り捨てを半々にし、この偏りを抑えます。
負の数に注意
DOWN と FLOOR は、正の数では同じ結果ですが、負の数では結果が変わります(-2.5 → DOWNは-2、FLOORは-3)。返金や値引きなどマイナスの金額を扱う場合は、どちらが業務ルールに合っているかを確認します。
どの丸めを使うかは、技術ではなく業務のルールで決まります。税額計算・利息計算・按分など、処理ごとに社内規程や契約で定められた丸め方があるので、仕様として確認し、コードに明示します。
比較:equalsではなくcompareToを使う
BigDecimal a = new BigDecimal("2.0");
BigDecimal b = new BigDecimal("2.00");
a.equals(b); // false(スケール=小数点以下の桁数も比較する)
a.compareTo(b) == 0; // true(値だけを比較する)
BigDecimalの equals は、値に加えてスケール(桁数)も一致しないとfalseになります。金額として同じかどうかを判定するときは compareTo を使います。
if (amount.compareTo(BigDecimal.ZERO) > 0) { ... } // 0より大きい
同じ理由で、BigDecimalをHashSetやHashMapのキーに使うと、2.0と2.00が別物として扱われます。キーにする場合は、stripTrailingZeros() でスケールを揃えるなどの対策が必要です。
表示するときの注意
new BigDecimal("1E+3").toString(); // "1E+3"(指数表記になることがある)
new BigDecimal("1E+3").toPlainString(); // "1000"
画面や帳票、CSVに出力するときは、指数表記を避けるために toPlainString() を使います。3桁区切りなどの書式は、NumberFormat や DecimalFormat で整えます。
BigDecimalを使わない選択肢:最小単位の整数で持つ
日本円のように小数が発生しない通貨で、端数処理のルールが単純な場合は、金額を「円」単位の long で持つ方法もあります。整数の計算には誤差がないため、シンプルで高速です。
ただし、税率や利率の計算、按分など、途中で小数が出る計算を含む場合は、BigDecimalで計算してから最後に丸めるのが安全です。
よくある質問
Q. BigDecimalは遅いですか? A. doubleに比べると遅いですが、業務システムの多くの処理では問題になりません。正確さが求められる金額計算では、速度より正確さを優先します。
Q. データベースの金額はどの型で持てばいいですか? A. DECIMAL(NUMERIC)型を使います。FLOATやDOUBLEの型で保存すると、Java側でBigDecimalを使っても誤差が入ります。
Q. 丸めはどのタイミングで行うべきですか? A. 途中の計算では丸めずに精度を保ち、業務ルールで決められたタイミング(明細ごと、合計時など)で丸めるのが基本です。どこで丸めるかによって結果が変わるため、仕様として明確にしておきます。
Q. BigDecimalをnullで扱ってもいいですか?
A. 「0円」と「未設定」を区別する必要がなければ、BigDecimal.ZERO を使う方が安全です。nullのまま計算するとNullPointerExceptionの原因になります。
まとめ
- 金額計算はdoubleではなくBigDecimal。データベースもDECIMAL型で
- 生成は文字列かvalueOf。
new BigDecimal(0.1)は誤差を取り込む - 計算結果は必ず受け取る(不変オブジェクト)
- 割り算は桁数と丸めモードを指定。丸め方は業務ルールで決める
- 比較はcompareTo。equalsはスケールまで比較する
- 出力はtoPlainStringで指数表記を避ける
株式会社RJCが携わる保険・金融・決済のシステムでは、1円のずれも許されない計算が日常です。こうした基本を確実に押さえることが、信頼されるシステムの土台になると考えています。
▶ RJC丸わかり https://www.r-jc.jp/guide/?mediacode=02-00001-003
▶ エントリーはこちら(1〜2分で完了します) https://www.r-jc.jp/recruitment/?mediacode=02-00001-003
株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。