首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 理論與實踐:浮點數與 BigDecimal 的陷阱與最佳實踐

     編輯:Java 相關知識

許多開發者在職業生涯中極少接觸非整數類型,除了偶爾在計時或基準測試中使用。Java 提供兩類非整數類型:基於 IEEE 754 標準的浮點數(float、double 及其包裝類)和任意精度的十進制數(java.math.BigDecimal)。本文深入探討在 Java 程式中使用這些類型時常見的陷阱與「gotcha」。

IEEE 754 浮點數機制

Java 的 float(32 位)和 double(64 位)遵循 IEEE 754 標準,採用科學記號以底數 2 表示:1 位符號位、指數位(float 8 位、double 11 位)和尾數位(float 23 位、double 52 位)。數值會被正規化為 1.xxxx × 2^exp 形式。

IEEE 754 浮點數格式

特殊值與比較陷阱

標準定義了正/負無窮大、正/負零和 NaN(非數字)。這些特殊值行為反直覺:

  • 0.0 == -0.0 為 true,但 Float.compareTo() 認為 -0.0 < 0.0
  • NaN 與任何值比較(包括自身)皆為 false,需用 Float.isNaN() 檢測
  • 基本型別與包裝類比較語義不同:Float.equals() 認為兩個 NaN 相等,以便作為 HashMap 鍵

浮點運算的常見錯誤假設

因特殊值存在,看似合理的代數恆等式可能失效:

表達式不一定等於當
0.0 - f-ff == 0
f < g!(f >= g)f 或 g 為 NaN
f == ftruef 為 NaN
f + g - gfg 為無窮大或 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) < epsilon
  • 檢查有效範圍時用「包含式」而非「排除式」:if (value >= 0 && value < Float.INFINITY) 可攔截 NaN

BigDecimal:精確十進制運算

JDK 1.3 引入 BigDecimal,內部以 unscaledValue × 10^(-scale) 表示,支援任意精度。適用於貨幣等需精確值的場景。

關鍵注意點:

  • equals() 視 100.00 和 100.000 為不相等,數值比較必須用 compareTo()
  • 除法可能產生無限循環小數,需顯式指定捨入模式與精度
  • 構造器選擇至關重要:new BigDecimal(double) 會攜帶二進制近似誤差,請改用 new BigDecimal(String) 或 new BigDecimal(int, int)

JDBC 交互建議

讀寫 SQL DECIMAL 欄位時,務必使用 PreparedStatement.setBigDecimal() 和 ResultSet.getBigDecimal(),避免浮點轉換損失精度。

總結

  • 浮點數適合表達本身不精確的測量值
  • 需要精確十進制(貨幣、會計)時,請使用 BigDecimal
  • 永遠記住:0.1 + 0.2 != 0.3 在二進制浮點世界裡是常態
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved