0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Effective Java 9.60: 正確な答えが必要ならば、floatとdoubleを避ける

Last updated at Posted at 2026-01-11

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 を返すなら誤差の許容や丸め方を明記する
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?