Effective Java Item 60:需要精確結果時避免使用 float 和 double
整理 Effective Java 書中 Item 60: Avoid float and double if exact answers are required 心得筆記
主旨
Java 的 float 和 double 是為了高效處理近似值而設計,特別適合科學與工程領域。
但若場景需要精確數值(例如金額計算),它們就會成為麻煩來源,甚至導致錯誤結果。
點出問題:以為的 1.03 - 0.42,其實是 0.6100000000000001
浮點數並不能精確表達像 0.1 這樣的十進位小數。這會導致得到看起來荒謬的答案:
System.out.println(1.03 - 0.42); // 輸出:0.6100000000000001
System.out.println(1.00 - 9 * 0.10); // 輸出:0.09999999999999998
就算選擇四捨五入顯示,也無法保證運算結果在邏輯上正確。
範例:有 $1,要買 10¢、20¢、30¢…… 的糖果
以下這段用 double 計算你能買幾個糖果,並顯示剩下的錢:
double funds = 1.00;
int itemsBought = 0;
for (double price = 0.10; funds >= price; price += 0.10) {
funds -= price;
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Change: $" + funds);
輸出結果:
3 items bought.
Change: $0.3999999999999999
這根本就錯!你應該能買 4 個才對,並剛好把錢花光。
解法一:使用 BigDecimal
final BigDecimal TEN_CENTS = new BigDecimal(".10");
int itemsBought = 0;
BigDecimal funds = new BigDecimal("1.00");
for (BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS)) {
funds = funds.subtract(price);
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Money left over: $" + funds);
輸出正確:
4 items bought.
Money left over: $0.00
注意:建構 BigDecimal 請用字串建構子,如 new BigDecimal("1.00"),避免使用 new BigDecimal(1.00),因為會帶入不精確的浮點值!
解法二:用 int 或 long 做「整數處理」
用整數代表最小單位(如:1 元 = 100 分),可以避開浮點誤差:
int itemsBought = 0;
int funds = 100;
for (int price = 10; funds >= price; price += 10) {
funds -= price;
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Cash left over: " + funds + " cents");
這種方法簡潔、快速、無誤差,只是需要自己處理單位轉換與格式化輸出。
比較三種做法
| 方法 | 精確度 | 執行速度 | 撰寫便利性 | 適用場景 |
|---|---|---|---|---|
| float/double | ❌ | ✅ | ✅ | 科學計算、不需完全精準的統計 |
| BigDecimal | ✅ | ❌ | ❌ | 金融、帳務、法規計算 |
| int/long | ✅ | ✅ | ✅ (需自己轉換單位) | 小額金錢運算、遊戲內金幣等 |
小結
除非只需要「大約值」,否則千萬別用 float 或 double 來處理金額或其他需要絕對正確性的資料。
實務建議:
- 金額精算 → 用 BigDecimal
- 搭配字串建構子避免誤差
- 支援進位規則(例如四捨五入)
- 單位明確、金額不大 → 用 int/long
- 1 元 = 100 分、1 天 = 86400 秒這種設計很常見
- 若金額會超過 18 位數,才考慮用 BigDecimal 取代 long
精確比速度更重要的場景,請遠離
float和double!
Read other posts