許多開發者在職業生涯中極少接觸非整數類型,除了偶爾在計時或基準測試中使用。Java 提供兩類非整數類型:基於 IEEE 754 標準的浮點數(float、double 及其包裝類)和任意精度的十進制數(java.math.BigDecimal)。本文深入探討在 Java 程式中使用這些類型時常見的陷阱與「gotcha」。
Java 的 float(32 位)和 double(64 位)遵循 IEEE 754 標準,採用科學記號以底數 2 表示:1 位符號位、指數位(float 8 位、double 11 位)和尾數位(float 23 位、double 52 位)。數值會被正規化為 1.xxxx × 2^exp 形式。

標準定義了正/負無窮大、正/負零和 NaN(非數字)。這些特殊值行為反直覺:
0.0 == -0.0 為 true,但 Float.compareTo() 認為 -0.0 < 0.0NaN 與任何值比較(包括自身)皆為 false,需用 Float.isNaN() 檢測Float.equals() 認為兩個 NaN 相等,以便作為 HashMap 鍵因特殊值存在,看似合理的代數恆等式可能失效:
| 表達式 | 不一定等於 | 當 |
|---|---|---|
0.0 - f | -f | f == 0 |
f < g | !(f >= g) | f 或 g 為 NaN |
f == f | true | f 為 NaN |
f + g - g | f | g 為無窮大或 NaN |
許多十進制小數(如 0.1)無法精確用二進制表示,導致累積誤差。例如:
double s = 0;
for (int i = 0; i < 26; i++) s += 0.1;
System.out.println(s); // 輸出 2.600000000000001
強制轉型更危險:(int)(29.0 * 0.01 * 100) 得到 28 而非 29。
比較最佳實踐:
== 比較浮點數Math.abs(a - b) < epsilon 或 Math.abs(a/b - 1) < epsilonif (value >= 0 && value < Float.INFINITY) 可攔截 NaNJDK 1.3 引入 BigDecimal,內部以 unscaledValue × 10^(-scale) 表示,支援任意精度。適用於貨幣等需精確值的場景。
關鍵注意點:
equals() 視 100.00 和 100.000 為不相等,數值比較必須用 compareTo()new BigDecimal(double) 會攜帶二進制近似誤差,請改用 new BigDecimal(String) 或 new BigDecimal(int, int)讀寫 SQL DECIMAL 欄位時,務必使用 PreparedStatement.setBigDecimal() 和 ResultSet.getBigDecimal(),避免浮點轉換損失精度。
BigDecimal0.1 + 0.2 != 0.3 在二進制浮點世界裡是常態