導入
Javaでの開発、特に金融系や決済システムにおいて「金額計算」は避けて通れない領域です。よくある失敗として、floatやdoubleで金額を保持し、比較演算子(==)を使って判定しようとするケースがあります。しかし、浮動小数点演算は2進数ゆえの誤差が発生しやすく、致命的なバグの温床となります。本記事では、金額計算のデファクトスタンダードであるjava.math.BigDecimalを用いた「厳密な比較」の作法を解説します。
基礎知識
Javaのdouble型はIEEE 754形式で数値を保持するため、例えば「0.1 + 0.2」が厳密に「0.3」になりません。これに対し、BigDecimalは数値を10進数の文字列として保持・計算するため、精度を保証できます。ここで重要なのは、BigDecimalの比較には「==」や「!=」といった参照演算子ではなく、専用のメソッドを使用しなければならないという点です。また、BigDecimalの比較には「equalsメソッド」と「compareToメソッド」の二種類が存在し、それぞれの挙動の違いを理解しておく必要があります。
実装/解決策
BigDecimalの比較において、最も注意すべきは「equalsメソッド」の挙動です。BigDecimalのequalsメソッドは、値(数値)だけでなく、スケール(小数点以下の桁数)も比較対象とします。
例えば、new BigDecimal(“1.0”) と new BigDecimal(“1.00”) をequalsで比較すると、値は同じでもスケールが異なるため「false」を返します。
一方、compareToメソッドは「0.0」なら等しい、「-1」なら小さい、「1」なら大きいという数値を返します。金額の比較では、スケールを無視して値の大小を判定できるcompareToの使用が原則です。
サンプルプログラム
以下のコードで、equalsとcompareToの挙動の違いと、正しい比較方法を確認してください。
import java.math.BigDecimal;
public class BigDecimalComparison {
public static void main(String[] args) {
BigDecimal val1 = new BigDecimal(“1.0”);
BigDecimal val2 = new BigDecimal(“1.00”);
// 1. equalsによる比較(スケールまで含めて厳密に一致するかを確認)
// 結果: false (1.0と1.00は異なるオブジェクトとみなされる)
System.out.println(“equalsの結果: ” + val1.equals(val2));
// 2. compareToによる比較(数値としての大小を確認)
// 戻り値が0であれば「数値として等しい」と判定できる
// 結果: true
boolean isNumericEqual = (val1.compareTo(val2) == 0);
System.out.println(“compareToによる判定結果: ” + isNumericEqual);
// 3. 金額比較の実践例
BigDecimal balance = new BigDecimal(“100.00”);
BigDecimal payment = new BigDecimal(“100”);
if (balance.compareTo(payment) >= 0) {
System.out.println(“支払い可能です。”);
} else {
System.out.println(“残高不足です。”);
}
}
}
応用・注意点
現場でのトラブルを防ぐためのポイントを3点挙げます。
1. コンストラクタの選択: new BigDecimal(double) を使用すると、doubleの誤差がそのままBigDecimalに持ち込まれます。必ず new BigDecimal(String) を使用してください。
2. compareToの定数利用: if (val.compareTo(BigDecimal.ZERO) > 0) のように、定数を利用することで可読性が向上します。
3. スケールの統一: どうしてもequalsを使いたい場合は、事前に .stripTrailingZeros() を呼び出して不要なゼロを除去し、スケールを揃える運用が必要ですが、基本的には金額比較にはcompareToを使うのが最も安全でバグを減らす最短ルートです。
これらを意識するだけで、金額計算に関わる論理バグの大部分は防ぐことができます。ぜひ次の実装から取り入れてみてください。

コメント