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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?