Java 基本類型 double/float 採用 IEEE 754 浮點數表示,無法精確表達許多十進制小數(如 0.1),導致財務、科學計算出現誤差:
System.out.println(0.1 + 0.2); // 0.30000000000000004 System.out.println(1.0 - 0.9); // 0.09999999999999998
java.math.BigDecimal 提供任意精度的有符號十進制數,完全解決精度丟失問題,是金融、電商、計量系統的標準選擇。
| 構造器 | 說明 | 推薦度 |
|---|---|---|
new BigDecimal(String val) |
精確解析十進制字符串,無精度損失 | ⭐⭐⭐⭐⭐ 強烈推薦 |
new BigDecimal(double val) |
直接使用 double 二進制近似值,會保留浮點誤差 | ❌ 禁止使用 |
new BigDecimal(long val) / int |
精確轉換整數 | ⭐⭐⭐⭐ 適用整數場景 |
BigDecimal.valueOf(double val) |
內部調用 Double.toString() 間接實現字符串構造,可接受 |
⭐⭐⭐⭐ 兼容舊代碼 |
最佳實踐: 所有來自數據庫、前端、配置的數值統一以字符串形式傳遞給 BigDecimal 構造器。
BigDecimal 為不可變對象,所有運算方法返回新對象,原對象不變:
BigDecimal a = new BigDecimal("15124");
BigDecimal b = new BigDecimal("15124");
// 加減乘除
BigDecimal sum = a.add(b); // 30248
BigDecimal diff = a.subtract(b); // 0
BigDecimal prod = a.multiply(b); // 228735376
BigDecimal quot = a.divide(b); // 1 (精確整除)
// 除法非整除必須指定精度與舍入模式,否則拋 ArithmeticException
BigDecimal div = a.divide(new BigDecimal("3"), 10, RoundingMode.HALF_UP); // 5041.3333333333
// 比較與轉換
int cmp = a.compareTo(b); // 0 相等
String str = a.toString(); // "15124"
double d = a.doubleValue(); // 15124.0 (可能精度損失)
long l = a.longValue(); // 15124
int i = a.intValue(); // 15124
| 模式 | 行為 | 示例 (保留 2 位) |
|---|---|---|
| UP | 遠離零方向捨入 | 1.555 → 1.56 ; -1.555 → -1.56 |
| DOWN | 趨向零方向捨入 (截斷) | 1.555 → 1.55 ; -1.555 → -1.55 |
| CEILING | 向正無窮方向捨入 | 1.555 → 1.56 ; -1.555 → -1.55 |
| FLOOR | 向負無窮方向捨入 | 1.555 → 1.55 ; -1.555 → -1.56 |
| HALF_UP | 四捨五入 (最常用) | 1.555 → 1.56 ; 1.554 → 1.55 |
| HALF_DOWN | 五捨六入 | 1.555 → 1.55 ; 1.556 → 1.56 |
| HALF_EVEN | 銀行家算法 (IEEE 754 默認) | 1.555 → 1.56 (前位偶) ; 1.545 → 1.54 |
| UNNECESSARY | 要求精確,否則拋異常 | 1.5 / 2 → 異常 |
public final class BigDecimalUtils {
private static final int DEFAULT_SCALE = 10;
private static final RoundingMode DEFAULT_ROUNDING = RoundingMode.HALF_UP;
private BigDecimalUtils() {}
public static BigDecimal of(String val) {
return val == null ? BigDecimal.ZERO : new BigDecimal(val.trim());
}
public static BigDecimal of(double val) {
return BigDecimal.valueOf(val);
}
public static BigDecimal add(String a, String b) { return of(a).add(of(b)); }
public static BigDecimal sub(String a, String b) { return of(a).subtract(of(b)); }
public static BigDecimal mul(String a, String b) { return of(a).multiply(of(b)); }
public static BigDecimal div(String a, String b) {
return of(a).divide(of(b), DEFAULT_SCALE, DEFAULT_ROUNDING);
}
public static BigDecimal div(String a, String b, int scale) {
return of(a).divide(of(b), scale, DEFAULT_ROUNDING);
}
public static String toPlainString(BigDecimal bd) {
return bd == null ? "0" : bd.stripTrailingZeros().toPlainString(); // 去除科學計數法與尾部零
}
}
new BigDecimal(0.1) 得到 0.10000000000000000555...。修正: new BigDecimal("0.1") 或 BigDecimal.valueOf(0.1)。add() subtract() multiply() divide()。ArithmeticException: Non-terminating decimal expansion。修正:總是調用 divide(divisor, scale, roundingMode)。new BigDecimal("1.0").equals(new BigDecimal("1.00")) 為 false (scale 不同)。修正:使用 compareTo() == 0。toString() 大數值產生 1E+10。修正:用 toPlainString() 或 stripTrailingZeros().toPlainString()。BigDecimal 是 Java 精確計算的基石。核心原則:字符串構造、不可變運算、除法必配精度與舍入、比較用 compareTo、輸出用 toPlainString。封裝工具類統一風格,可將精度問題徹底隔離在業務代碼之外。