プログラミング開発

【Java】BigDecimal完全ガイド:丸め方・比較方法・注意点を徹底解説

この記事は約6分で読めます。

はじめに

プログラム上で小数をきちんと扱えていますか…?

Javaで小数を扱う際に、floatdouble をそのまま使うと、

0.1 + 0.2 = 0.30000000000000004

のような誤差に悩まされることがあります。この誤差を避けるために登場するのが BigDecimal クラスです。

しかし、BigDecimal は使えば良いものでなく、扱い方を誤ると逆にバグの温床にもなります。
この記事では、BigDecimalの基本から丸め、比較、equalsの注意点までを体系的に解説します

BigDecimalとは?

BigDecimal高精度な10進数演算 をサポートするクラスです。主に以下のような用途で使われます。

  • 金額・税計算など、誤差を許容できない処理
  • 科学計算・統計処理など、小数点以下を正確に扱う演算

なぜBigDecimal を使うのか?

まず前提として、float や double は誤差を含む型です。コンピュータ内部では 2進数(0と1) で数値を表現しています。ところが、10進数の0.1や0.2は2進数で「有限桁」で表現できません。たとえば、0.1 を2進数に変換すると次のように「無限に続く小数」になります。

0.1(10進数) = 0.0001100110011001100110011...(2進数)

このように 循環小数(無限小数) になるため、コンピュータは途中で打ち切って「近似値」として保持します。このわずかな誤差が、計算を繰り返すうちに積み重なり、結果として

0.1 + 0.2 = 0.30000000000000004

のようなズレが発生します。

BigDecimalの正しい生成方法

import java.math.BigDecimal;

BigDecimal a = new BigDecimal("0.1");   // ✅ 安全
BigDecimal b = BigDecimal.valueOf(0.1); // ✅ 安全(内部で文字列化)
BigDecimal c = new BigDecimal(0.1);     // ❌ 危険:誤差を含む

new BigDecimal(double) は危険!

System.out.println(new BigDecimal(0.1)); // 0.1000000000000000055511151231...

内部の2進表現がそのまま反映されるため、必ず文字列か valueOf() を使うのがベストです。

誤差を含んだdouble型で生成するから、結局誤差があるものが生み出されるんだね…。

基本的な演算

BigDecimal price = new BigDecimal("1200");
BigDecimal taxRate = new BigDecimal("0.1");

BigDecimal total = price.multiply(BigDecimal.ONE.add(taxRate));
System.out.println(total); // 1320.0

主な演算メソッド

メソッド説明
add()加算
subtract()減算
multiply()乗算
divide()除算(※注意点あり)

divide() には要注意

除算は、結果が有限小数でない場合に ArithmeticException を投げます。

BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("3");
System.out.println(a.divide(b)); // ⚠️ 例外:Non-terminating decimal expansion

これを防ぐには、丸めモード(RoundingMode) を指定します。

丸め(RoundingMode)の使い方

import java.math.RoundingMode;

BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("3");

BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP);
System.out.println(result); // 3.33

主なRoundingMode一覧

モード説明例(1.2345 → 2桁)
HALF_UP四捨五入1.23 → 1.23
1.235 → 1.24
HALF_DOWN五捨六入1.235 → 1.23
HALF_EVEN偶数丸め(銀行丸め法)
もっとも近い数字に
1.235 → 1.24
1.245 → 1.24
UP常に切り上げ
(0から遠ざかる方向へ)
1.231 → 1.24
-1.231 → -1.24
DOWN常に切り捨て
(0に近づく方向へ)
1.239 → 1.23
-1.239 → -1.23
CEILING正方向に丸め1.239 → 1.23
-1.239 → -1.23
FLOOR負方向に丸め1.239 → 1.23
-1.239 → -1.24

やりたいことをきちんと理解して、選択する必要がありそうだね!

比較は、equals() ではなく compareTo()

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");

System.out.println(a.equals(b));    // false
System.out.println(a.compareTo(b)); // 0(等しい)

なぜ違う?

  • equals()スケール(小数点以下の桁数)まで比較します。
    → つまり、1.01.00 は別物と判断されます。
  • compareTo()数値的な値を比較します。
    → 同じ数値であればスケールが違っても 0 を返します。

大小比較

BigDecimal a = new BigDecimal("10.5");
BigDecimal b = new BigDecimal("9.8");

System.out.println(a.compareTo(b) > 0);  // true(a > b)
System.out.println(a.compareTo(b) == 0); // false(a == b)
System.out.println(a.compareTo(b) < 0);  // false(a < b)

数値の大小比較は必ず compareTo() を使うのが鉄則です。

setScale() で桁数を揃える

BigDecimal price = new BigDecimal("123.4567");
BigDecimal rounded = price.setScale(2, RoundingMode.HALF_UP);
System.out.println(rounded); // 123.46

setScale() は小数点以下の桁数を統一するのに便利です。
金額処理では「常に2桁(円未満切り上げ)」などの指定がよく行われます。


まとめ

BigDecimal は一見難しく見えますが、生成方法」「丸め」「比較の3点さえ押さえれば怖くありません。金額や税計算など誤差が許されない場面では、「double は使わず、BigDecimal + RoundingMode + compareTo()」が鉄則です。

  • 小数をあつかう、特に金額計算などナイーブなものにはBigDecimalを使う。
  • 初期化時の引数は文字列を指定する。
  • 比較はcompareToに使う。