double result = 4.015 * 100; System.out.println(result); // 401.49999999999994 ❌ // 預期:401.5
十進制 0.1、0.4 等無法用有限二進制精確表示:
0.4 (十進制) = 0.011001100110011... (二進制無限循環) double 存儲:符號位1 + 指數11 + 尾數52 → 截斷誤差不可避免
商業計算必須用 BigDecimal;科學/工程計算可用 float/double。 —— Effective Java
| 構造方式 | 代碼 | 結果 | 評價 |
|---|---|---|---|
| double 值構造 | new BigDecimal(4.015) | 4.014999999999999... | ❌ 已含誤差 |
| 字符串構造 | new BigDecimal("4.015") | 4.015 | ✅ 精確 |
| valueOf 工廠 | BigDecimal.valueOf(4.015) | 4.015 | ✅ 推薦(內部用 toString) |
BigDecimal a = new BigDecimal("4.015");
BigDecimal b = new BigDecimal("100");
BigDecimal add = a.add(b); // 加法
BigDecimal sub = a.subtract(b); // 減法
BigDecimal mul = a.multiply(b); // 乘法
BigDecimal div = a.divide(b, 2, RoundingMode.HALF_UP); // 除法(必須指定精度與舍入模式)
NumberFormat fmt = NumberFormat.getCurrencyInstance(); fmt.setMaximumFractionDigits(2); System.out.println(fmt.format(mul)); // ¥401.50
// 核心結構
public class BigDecimal extends Number implements Comparable<BigDecimal> {
private final BigInteger intVal; // 無標度值(任意大整數)
private final int scale; // 標度(小數點右移位數)
// 表示值 = intVal × 10^(-scale)
// 例:4.015 → intVal=4015, scale=3
}
public class DecimalUtil {
private static final int DEF_SCALE = 10;
private static final RoundingMode DEF_ROUND = RoundingMode.HALF_UP;
public static BigDecimal add(String a, String b) {
return new BigDecimal(a).add(new BigDecimal(b));
}
public static BigDecimal mul(String a, String b) {
return new BigDecimal(a).multiply(new BigDecimal(b));
}
public static BigDecimal div(String a, String b) {
return div(a, b, DEF_SCALE);
}
public static BigDecimal div(String a, String b, int scale) {
return new BigDecimal(a).divide(new BigDecimal(b), scale, DEF_ROUND);
}
public static String toPlainString(BigDecimal bd) {
return bd.stripTrailingZeros().toPlainString(); // 去尾部零
}
}
1/3 拋 ArithmeticException2.0.equals(2.00) 為 false,用 compareTo() == 0浮點數精度問題源於二進制表示本質缺陷。BigDecimal + 字符串構造 + 顯式精度控制 是商業系統的鐵律。封裝工具類統一入口,代碼審查攔截 double 參與金額運算,從源頭杜絕精度 Bug。