9.60: 正確な答えが必要ならば、floatとdoubleを避ける
結論
金額や精度が厳密に求められる計算(会計、課金、スコアの厳密比較など)では、float/double(浮動小数点)を避け、整数(long/BigInteger)でスケールを管理するか BigDecimal を使うべき。
浮動小数点は「近似」を扱う設計のため、10進小数の正確な表現や厳密比較に弱く、誤差・丸め・NaN/無限大などによるバグを招きやすい。
良い例
金額を扱う(通貨単位:円・セント等)は整数でスケール管理する方法(推奨:高速でわかりやすい)と、精密な小数計算に BigDecimal を使う方法。
// (A) 整数で cents を管理する(パフォーマンス良・単純)
public class Price {
// 単位は「セント」など最小単位で管理
private final long cents;
public Price(long cents) {
this.cents = cents;
}
public static Price ofYen(long yen) { return new Price(yen * 100); } // 例: 100円単位なら scale=100
public long getCents() { return cents; }
public Price add(Price other) { return new Price(this.cents + other.cents); }
public Price multiply(int n) { return new Price(this.cents * n); }
@Override public String toString() { return (cents / 100) + "." + String.format("%02d", cents % 100); }
}
// (B) BigDecimal を正しく使う(高精度・小数が自然な場合)
import java.math.BigDecimal;
import java.math.RoundingMode;
BigDecimal price = new BigDecimal("0.10"); // 正:文字列コンストラクタを使う
BigDecimal tax = new BigDecimal("0.08");
BigDecimal total = price.add(price.multiply(tax)).setScale(2, RoundingMode.HALF_UP);
// BigDecimal の等価比較は compareTo を使う(equals は scale も見る)
if (total.compareTo(new BigDecimal("0.11")) == 0) {
// 等価
}
ポイント:
- 通貨は最小単位(セント/銭/厘)で整数管理すると丸めの扱いが明確で高速
-
BigDecimalを使う場合、new BigDecimal(String)かBigDecimal.valueOf(double)を使い、new BigDecimal(double)は避ける -
BigDecimal.equals()はスケールも含むため数値比較にはcompareTo()を使う。丸めはsetScale()とRoundingModeで明示する
悪い例
浮動小数点特有の誤差を招く典型例と、BigDecimal の誤用例。
// (1) double の誤差例
double a = 0.1;
double b = 0.2;
System.out.println(a + b == 0.3); // false !! 0.1 + 0.2 は 0.30000000000000004 になる
// (2) new BigDecimal(double) の落とし穴
BigDecimal bad = new BigDecimal(0.1); // NG: 内部 double の二進表現が入る => 0.100000000000000005551...
BigDecimal good = new BigDecimal("0.1"); // OK: 期待どおり 0.1 を表現
// (3) BigDecimal.equals の罠
BigDecimal x = new BigDecimal("1.0");
BigDecimal y = new BigDecimal("1.00");
System.out.println(x.equals(y)); // false (スケールが違う)
System.out.println(x.compareTo(y) == 0); // true (数値として等しい)
なぜこれが問題か:
- 浮動小数点は 10 進小数を二進で近似するため
0.1のような値は正確に表現できない。比較・集計・丸めに誤差が残る -
new BigDecimal(double)は double の近似値をそのまま取り込むため意図しない桁が入る
まとめ
-
必要なときだけ浮動小数点を使う: 科学計算や近似で良い場合は
double/floatを使ってOK
-
金額や固定小数点が必要なときは整数でスケール管理(例:
long cents)。最もシンプルで高速で扱いやすい
-
複雑な小数(高精度)が必要なら
BigDecimalを使う-
new BigDecimal("0.1")またはBigDecimal.valueOf(0.1)を使う(new BigDecimal(double)は避ける) - 丸めは常に明示的に
setScale(..., RoundingMode)で指定する - 比較は
compareTo()を使用(equals()はスケールも考慮する)
-
-
表示用のフォーマットと内部表現は分ける: 表示は
NumberFormat/DecimalFormatで行い、内部は上のどれかで正確に管理する
-
単体テストで端数・累積誤差を確認: 境界ケース(四捨五入、累積和、非常に大きい/小さい値)を必ずテストする
-
パフォーマンス考慮:
BigDecimalはオブジェクトで遅い。ホットパスでは整数スケール方式を検討する
-
浮動小数点の特性をドキュメント化: API の戻り値が
doubleを返すなら誤差の許容や丸め方を明記する