なぜ BigDecimal に「丸めモード」が必要なのか(全体像)
BigDecimal は「小数を誤差なく扱う」ためのクラスですが、
もう一つ大事なテーマがあります。
「小数をどこかで“切り捨て・切り上げ・四捨五入”しないといけないとき、そのルールをどう決めるか」
これが「丸めモード(RoundingMode)」です。
例えば、
1.005 を小数第2位までにしたいとき、
1.00 にするのか、1.01 にするのか、場合によってルールが違います。
お金・税金・利息・料金など、現実世界のルールが絡む計算では、
この「丸め方」を間違えると、ビジネスロジックとしてアウトになります。
BigDecimal の丸めモードは、
どの桁で切るか(scale: 小数桁数)
どう切るか(RoundingMode: 丸め方)
を、コードの中でハッキリ指定するための仕組みです。
基本の使い方:setScale と RoundingMode の組み合わせ
setScale で「小数点以下何桁にするか」を指定する
BigDecimal の桁数を揃えたいときに使うのが setScale です。
BigDecimal x = new BigDecimal("1.2345");
BigDecimal y = x.setScale(2, RoundingMode.HALF_UP); // 小数第2位まで
System.out.println(y); // 1.23
Javaここで
第1引数:2 → 小数点以下 2 桁にする
第2引数:RoundingMode.HALF_UP → 一般的な四捨五入
を意味します。
「丸めモード」の話は、この RoundingMode.◯◯ の部分が主役です。
まずはこれだけ覚えればいい 3 モード(超重要)
丸めモードはたくさんありますが、
初心者のうちは、まず「この3つ」をしっかり押さえるのがおすすめです。
HALF_UP:いわゆる普通の四捨五入
一番なじみのあるルールです。
- 0〜4 → 切り捨て
- 5〜9 → 切り上げ
の、学校で習った「四捨五入」です。
BigDecimal x1 = new BigDecimal("1.234");
BigDecimal x2 = new BigDecimal("1.235");
BigDecimal x3 = new BigDecimal("1.236");
System.out.println(x1.setScale(2, RoundingMode.HALF_UP)); // 1.23
System.out.println(x2.setScale(2, RoundingMode.HALF_UP)); // 1.24
System.out.println(x3.setScale(2, RoundingMode.HALF_UP)); // 1.24
Java多くの「通常の金額計算」は、この HALF_UP で進めることが多いです。
(ただし、業界によって別ルールを求められることもあります)
DOWN:単純な切り捨て(0 方向)
RoundingMode.DOWN は「小数点以下を全部捨てる」イメージです。
0 の方向に寄せる、と思ってください。
BigDecimal x1 = new BigDecimal("1.239");
BigDecimal x2 = new BigDecimal("-1.239");
System.out.println(x1.setScale(2, RoundingMode.DOWN)); // 1.23
System.out.println(x2.setScale(2, RoundingMode.DOWN)); // -1.23
Javaポイントは、マイナスでも「0 に近づく」形になることです。
1.239 → 1.23
-1.239 → -1.23
となります。
「常にお客さんに不利にならないよう、1円未満は切り捨て」
のようなルールなら、この DOWN を選ぶことがあります。
UP:単純な切り上げ(0 から離れる方向)
RoundingMode.UP は DOWN の逆で、「0 から離れる方向」に切り上げます。
BigDecimal x1 = new BigDecimal("1.231");
BigDecimal x2 = new BigDecimal("-1.231");
System.out.println(x1.setScale(2, RoundingMode.UP)); // 1.24
System.out.println(x2.setScale(2, RoundingMode.UP)); // -1.24
Java1.231 → 1.24
-1.231 → -1.24
マイナス値も「絶対値が大きくなる方向」に動きます。
「必ず会社側に有利な方向に丸める」
「容量制限を超えないよう、少し余裕を見て上に丸める」
といった特殊なルールで使われます。
HALF_EVEN(銀行丸め)を直感で理解する
なぜ「5 をいつも切り上げる」のがダメな場合があるのか
HALF_UP(普通の四捨五入)は、「.5 は必ず上に行く」ルールです。
大量のデータを丸め続けると、少しずつ「上方向に偏った誤差」が積み上がります。
金融・統計・大規模集計の世界では、この「偏り」が問題になることがあります。
そこで登場するのが RoundingMode.HALF_EVEN、いわゆる「銀行丸め」です。
HALF_EVEN のルール
ざっくり言うと、
「ちょうど真ん中 (.5) のときだけ、“結果の下1桁が偶数になる方” に丸める」
というルールです。
例として、小数第1位で丸めてみます。(setScale(0, HALF_EVEN) と同じイメージ)
1.5 → 2(2 は偶数)
2.5 → 2(2 は偶数)
3.5 → 4(4 は偶数)
4.5 → 4(4 は偶数)
Java のコードで、もう少し現実的な例を見ます。
BigDecimal a = new BigDecimal("1.25");
BigDecimal b = new BigDecimal("1.35");
System.out.println(a.setScale(1, RoundingMode.HALF_UP)); // 1.3
System.out.println(b.setScale(1, RoundingMode.HALF_UP)); // 1.4
System.out.println(a.setScale(1, RoundingMode.HALF_EVEN)); // 1.2
System.out.println(b.setScale(1, RoundingMode.HALF_EVEN)); // 1.4
JavaHALF_UP では、1.25 も 1.35 も「5 なので上へ」で、
それぞれ 1.3, 1.4 になります。
HALF_EVEN では、
1.25 → 1.2(下1桁が偶数になる 1.2 へ)
1.35 → 1.4(下1桁が偶数になる 1.4 へ)
というように、.5 のときだけ「偶数側」に寄せます。
大量に丸めると、「上に行く回数」と「下に行く回数」がバランスしやすくなり、
「偏りの少ない丸め」が得られることが期待されます。
銀行や統計処理など、「長期的な公平さ」が求められる世界でよく使われます。
丸めモードがないとどうなるか(ArithmeticException)
divide で丸めモードを指定しないと怒られるケース
BigDecimal の割り算でよくあるのが、「割り切れないのに丸めモードを指定しない」問題です。
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("3");
BigDecimal c = a.divide(b); // ここで ArithmeticException: Non-terminating decimal expansion
Java1 ÷ 3 は 0.3333… と無限に続く小数です。
「どこまで続けるの?」を指定しないまま divide すると、Java は怒ります。
この場合は、scale と RoundingMode をセットで指定します。
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("3");
BigDecimal c = a.divide(b, 2, RoundingMode.HALF_UP); // 小数第2位まで四捨五入
System.out.println(c); // 0.33
Javaあるいは、「scale は後から setScale で揃える」という考え方もありますが、
割り算は最初から「どこで丸めるか」を決めておいたほうが安全です。
どの丸めモードを選ぶかを判断する軸
ビジネスルールに合わせるのが最優先
正解は「一般論」ではなく、「そのシステムのルール」です。
例えば:
・一般的な金額表示
→ 小数第2位(1円)まで HALF_UP(普通の四捨五入)
・端数は必ずお客さんに有利にしたい(1円未満は切り捨て)
→ DOWN
・容量や枠を超えないように、常に安全側に見積もる
→ UP
・長期的な統計・利息・精算などで偏りを減らしたい
→ HALF_EVEN
という感じで、「現実世界のルール」をまず確認してから、
それに対応する RoundingMode を選びます。
コード内で「何をしたいか」が伝わることが大事
例えば、「税込価格を計算して、小数第2位まで四捨五入する」なら:
BigDecimal taxRate = new BigDecimal("0.1");
BigDecimal price = new BigDecimal("123");
BigDecimal tax = price.multiply(taxRate); // 12.3
BigDecimal total = price.add(tax).setScale(2, RoundingMode.HALF_UP); // 135.30
Javaここで、setScale(2, RoundingMode.HALF_UP) と書いてあると、
「1円単位で四捨五入しているんだな」と読み手にも伝わります。
丸めモードをコードの中で明示することは、
「バグを防ぐ」というより、「仕様をコードに刻む」意味合いが大きいです。
まとめ:BigDecimal の丸めモードをどう捉えておくか
初心者として、まずはこのように整理できていれば十分です。
- BigDecimal は「どの桁で」「どう丸めるか」を自分で決めてあげないといけない
- setScale(桁数, RoundingMode.◯◯) で、「桁」と「丸め方」をセットで指定する
- よく使うのは HALF_UP(普通の四捨五入)、DOWN(切り捨て)、UP(切り上げ)、必要に応じて HALF_EVEN(銀行丸め)
- divide で割り切れないときは、必ず丸めモードを指定する
