なぜ BigDecimal の「加算・減算」が特別なのか(全体像)
BigDecimal は「お金など誤差が許されない小数」を扱うためのクラスでしたね。
その代わり、+ や - の演算子は使えません。
+ や - の代わりに
- 加算 →
add - 減算 →
subtract
というメソッドを使います。
ここを曖昧なまま書き始めると、
+が使えないことにイライラして変な変換をし始めるnew BigDecimal(double)を混ぜて誤差を持ち込んでしまうnullやマイナス値の扱いでバグる
といったことになりがちです。
「どう足す・どう引く」がパターンとして頭に入っていると、金額ロジックを書くのが一気に楽になります。
基本の形:add / subtract でシンプルに足す・引く
まずは素朴な加算から
BigDecimal 同士の加算は add で行います。
import java.math.BigDecimal;
BigDecimal a = new BigDecimal("100.50");
BigDecimal b = new BigDecimal("200.25");
BigDecimal sum = a.add(b); // 100.50 + 200.25
System.out.println(sum); // 300.75
Javaここで重要なのは、
aやbの中身は変更されない(BigDecimal は不変オブジェクト)- 戻り値として「新しい BigDecimal」が返る
という点です。
つまり、「代入し直さないと、元の変数は変わらない」という感覚を持っておく必要があります。
BigDecimal total = BigDecimal.ZERO;
total.add(new BigDecimal("100")); // total は変わらない!
System.out.println(total); // 0
total = total.add(new BigDecimal("100")); // こう書いて初めて 100 になる
System.out.println(total); // 100
Javaこの「戻り値をちゃんと受け取る」が、BigDecimal を扱ううえでとても重要です。
減算は subtract を使う
減算は subtract で行います。
BigDecimal a = new BigDecimal("300");
BigDecimal b = new BigDecimal("120");
BigDecimal diff = a.subtract(b); // 300 - 120
System.out.println(diff); // 180
Javaイメージとしては、
a.add(b)→a + ba.subtract(b)→a - b
と対応しています。
金額の「累計」を足していくパターン(ここはよく使う)
典型的なパターン:for ループで合計を求める
商品の金額を合計する例で考えてみましょう。
BigDecimal[] prices = {
new BigDecimal("100.50"),
new BigDecimal("200.25"),
new BigDecimal("50.25")
};
BigDecimal total = BigDecimal.ZERO; // 合計は 0 からスタート
for (BigDecimal price : prices) {
total = total.add(price); // 戻り値で total を更新
}
System.out.println("合計: " + total); // 351.00
Javaここでのポイントは、
- 初期値は
BigDecimal.ZEROを使う(new BigDecimal("0")より読みやすく安全) total = total.add(price);のように、「毎回代入し直す」ことを忘れない
という 2 点です。
total.add(price); と書くだけで満足してしまうと、total は変わりません。
初心者がかなりやりがちなバグなので、「BigDecimal は不変。合計は必ず代入し直す」と体に覚えさせてください。
マイナス値・返金・割引などでの減算の考え方
subtract で「引く」か、「マイナスを足す」か
例えば、注文金額から割引額を引きたいとします。
BigDecimal amount = new BigDecimal("1000"); // 注文金額
BigDecimal discount = new BigDecimal("200"); // 割引額
BigDecimal finalAmount = amount.subtract(discount); // 1000 - 200 = 800
System.out.println(finalAmount); // 800
Javaシンプルに subtract を使えば OK です。
一方で、「マイナスの値を add する」という書き方もできます。
BigDecimal amount = new BigDecimal("1000");
BigDecimal discount = new BigDecimal("200");
BigDecimal finalAmount = amount.add(discount.negate()); // discount.negate() は -200
System.out.println(finalAmount); // 800
Javanegate() は「符号を反転する」メソッドです。
discountが 200 →discount.negate()は -200add(-200)→ 実質 subtract になる
subtract を素直に使うか、negate() と組み合わせて add を使うかは好みですが、
「割引や返金を“マイナスの売上”として扱う」ような設計では negate() が出てくることが多いです。
new BigDecimal(…) の罠を、加算・減算でも絶対に避ける
double から new BigDecimal(double) しない(重要)
例えば、こういうコードはやってはいけません。
double price = 0.1;
BigDecimal a = new BigDecimal(price); // NG
BigDecimal total = BigDecimal.ZERO;
total = total.add(a);
Javaprice が double の時点で、すでに誤差を含んだ値になっています。
その誤差ごと BigDecimal に持ち込んでしまうと、
「BigDecimal を使っているのに、きっちりした計算になっていない」
という本末転倒な状況になります。
BigDecimal はできるだけ最初から文字列で作る
(もしくは、整数部分とスケールを明確に決めてから作る)
というのが鉄則です。
BigDecimal a = new BigDecimal("0.1"); // これなら OK
Java加算・減算そのものよりも、この「どう作るか」の方がずっと大事だと思ってください。
「null かもしれない BigDecimal を足したい」ときの考え方
null のまま add すると即 NullPointerException
DB から取ってきた値などで、BigDecimal が null のことがあります。
BigDecimal total = null;
total = total.add(new BigDecimal("100")); // NullPointerException
Javaこれもよくある事故です。
合計処理などで「最初は null で、そのうち値が入るかも」という設計は、
BigDecimal とは相性が悪いです。
一番シンプルな対処は、「最初から 0 にしておくこと」です。
BigDecimal total = BigDecimal.ZERO; // 最初から 0 にしておく
total = total.add(new BigDecimal("100")); // OK
Javaもしどうしても null が紛れ込む可能性があるなら、
「null を 0 として扱う」というルールをどこかで一度決めてしまうと、
以降の加算ロジックが楽になります。
例えば:
BigDecimal safe(BigDecimal value) {
return (value != null) ? value : BigDecimal.ZERO;
}
total = safe(total).add(new BigDecimal("100"));
Javaですが、設計としては「合計は最初から 0 にする」の方がシンプルでおすすめです。
まとめ:BigDecimal の加算・減算で初心者が押さえるべきポイント
BigDecimal の足し算・引き算は、初心者のうちにこう整理しておくと楽になります。
+/-は使えない。加算はadd、減算はsubtract。- BigDecimal は不変なので、
total = total.add(x);のように「戻り値で上書き」する必要がある。 - 合計の初期値は
BigDecimal.ZEROを使うと安全で読みやすい。 - 割引や返金では
subtractかnegate()を組み合わせたaddを使う設計が多い。 new BigDecimal(double)で誤差を持ち込まない。文字列から作るのが基本。- 合計を
nullスタートにしない。0 スタートにするか、nullを 0 として扱う場所を1か所に決める。
