Java float/double 採用 IEEE 754 二進位浮點數,無法精確表示許多十進位小數(如 0.1),導致商業計算出現誤差。BigDecimal 提供任意精度定點數運算,是金融、電商系統的標準選擇。
問題演示
System.out.println(0.06 + 0.01); // 0.06999999999999999
System.out.println(1.0 - 0.42); // 0.5800000000000001
System.out.println(4.015 * 100); // 401.49999999999994
System.out.println(303.1 / 1000); // 0.30310000000000004
原因:二進位無法精確表示 0.1、0.01 等十進位小數,CPU 存儲為近似值。
BigDecimal 核心用法
1. 建構物件(避坑指南)
// ❌ 錯誤:double 建構子繼承二進位誤差
new BigDecimal(0.1); // 0.1000000000000000055511151231257827021181583404541015625
// ✅ 正確:字串建構子精確保留十進位字面值
new BigDecimal("0.1"); // 0.1
// ✅ 正確:valueOf 靜態工廠(內部用 Double.toString)
BigDecimal.valueOf(0.1); // 0.1
// ✅ 正確:從長整數 + 模數構建(高效能場景)
BigDecimal.valueOf(12345L, 3); // 12.345
2. 四則運算(不可變模式)
BigDecimal a = new BigDecimal("10.5");
BigDecimal b = new BigDecimal("2.3");
a.add(b); // 12.8
a.subtract(b); // 8.2
a.multiply(b); // 24.15
a.divide(b); // 拋 ArithmeticException:非終結小數
a.divide(b, 2, RoundingMode.HALF_UP); // 4.57(指定精度與捨入模式)
3. 捨入模式
| 模式 | 行為 | 範例 (保留 2 位) |
| HALF_UP | 四捨五入 | 1.235 → 1.24 |
| HALF_DOWN | 五捨六入 | 1.235 → 1.23 |
| HALF_EVEN | 銀行家算法(向偶數捨入) | 1.235 → 1.24, 1.225 → 1.22 |
| UP | 遠離零方向 | 1.231 → 1.24 |
| DOWN | 趨向零方向(截斷) | 1.239 → 1.23 |
4. 比較與轉換
a.compareTo(b); // 1 (a>b), 0 (相等), -1 (a
通用工具類 Arith
public final class Arith {
private Arith() {}
private static final int DEF_SCALE = 10;
private static final RoundingMode DEF_MODE = RoundingMode.HALF_UP;
public static double add(double v1, double v2) {
return BigDecimal.valueOf(v1).add(BigDecimal.valueOf(v2)).doubleValue();
}
public static double sub(double v1, double v2) {
return BigDecimal.valueOf(v1).subtract(BigDecimal.valueOf(v2)).doubleValue();
}
public static double mul(double v1, double v2) {
return BigDecimal.valueOf(v1).multiply(BigDecimal.valueOf(v2)).doubleValue();
}
public static double div(double v1, double v2, int scale) {
if (scale < 0) throw new IllegalArgumentException("scale < 0");
return BigDecimal.valueOf(v1)
.divide(BigDecimal.valueOf(v2), scale, DEF_MODE)
.doubleValue();
}
public static double div(double v1, double v2) {
return div(v1, v2, DEF_SCALE);
}
public static double round(double v, int scale) {
return BigDecimal.valueOf(v).setScale(scale, DEF_MODE).doubleValue();
}
}
最佳實踐清單
- 永遠用字串或 valueOf 建構,禁用
new BigDecimal(double)。
- 除法必須指定精度與捨入模式,否則非終結小數拋異常。
- 比較用 compareTo,不要用
equals(模數敏感)。
- 金額儲存用長整數(分/厘),資料庫
DECIMAL(19,4),避免浮點數完全。
- 高效能場景:考慮
Apache Commons Math 或手寫定點數運算(長整數 + 固定小數位)。
總結
BigDecimal 解決浮點數精度問題的核心:十進位定點數表示 + 顯式精度控制。商業系統務必統一使用,資料庫層面同步採用 DECIMAL 型別,端到端消除精度丟失風險。
- 上一篇文章: Java 尋找所有水仙花數:三位數字立方和等於自身的完整實作
- 下一篇文章: Java 基礎入門:編寫第一個程式並調用自定義方法
Copyright ©
程式碼之家 All Rights Reserved