float/double 遵循 IEEE 754 二進制浮點數,**無法精確表示十進制小數**(如 0.1、1.22)。財務、電商、計量等精度敏感場景**必須使用 BigDecimal**。
| 構造器 | 示例 | 結果 | 推薦度 |
|---|---|---|---|
new BigDecimal(double) | new BigDecimal(1.22) | 1.21999999999999997335... | ❌ 禁用 |
new BigDecimal(String) | new BigDecimal("1.22") | 1.22 (精確) | ✅ 首選 |
BigDecimal.valueOf(double) | BigDecimal.valueOf(1.22) | 1.22 (經過 Double.toString) | ✅ 兼容 double 來源 |
new BigDecimal(char[]) | new BigDecimal(new char[]{'1','.','2','2'}) | 1.22 | ⭐ 高性能場景 |
核心原則:字符串構造最精確,double 必經 valueOf()。
BigDecimal a = new BigDecimal("1.22");
BigDecimal b = new BigDecimal("2.22");
a.add(b); // 返回新對象 3.44,a 仍為 1.22
BigDecimal c = a.add(b); // 正確:接收返回值
同理 subtract/multiply/divide 皆返回新對象,鏈式調用:a.add(b).multiply(c)。
// ❌ 無限循環小數拋 ArithmeticException
BigDecimal d = a.divide(b);
// ✅ 指定刻度與舍入
BigDecimal d = a.divide(b, 10, RoundingMode.HALF_UP); // 保留 10 位,四捨五入
BigDecimal d2 = a.divide(b, MathContext.DECIMAL128); // 用 MathContext 統一精度上下文
HALF_UP:四捨五入 (最常用)HALF_DOWN:五捨六入HALF_EVEN:銀行家舍入 (縮減統計偏差)DOWN:截斷 (向零)UP:遠離零CEILING/FLOOR:向正/負無窮BigDecimal x = new BigDecimal("1.00");
BigDecimal y = new BigDecimal("1.0");
x.equals(y); // false (scale 不同:2 vs 1)
x.compareTo(y); // 0 (數值相等)
x.stripTrailingZeros().equals(y.stripTrailingZeros()); // true
排序、去重、Map Key 必用 compareTo。
BigDecimal amt = new BigDecimal("1234.5");
// 保留 2 位,分組分隔符,貨幣符號
NumberFormat fmt = NumberFormat.getCurrencyInstance(Locale.CHINA);
fmt.setMaximumFractionDigits(2);
fmt.setMinimumFractionDigits(2);
System.out.println(fmt.format(amt)); // ¥1,234.50
// 科學計數
System.out.println(amt.toEngineeringString()); // 1.2345E3
static final BigDecimal ZERO = BigDecimal.ZERO;long 整數分/厘存儲,只在入口/出口轉 BigDecimal| 坑 | 代碼 | 後果 | 正確 |
|---|---|---|---|
| double 構造 | new BigDecimal(0.1) | 0.10000000000000000555 | new BigDecimal("0.1") / valueOf(0.1) |
| 忽略返回值 | a.add(b); | a 不變,邏輯錯誤 | a = a.add(b) |
| 除法不指定精度 | a.divide(b) | ArithmeticException | a.divide(b, scale, mode) |
| equals 比較 | x.equals(y) | scale 不同返回 false | x.compareTo(y) == 0 |
| 序列化精度丟失 | JSON 默認序列化 | 科學計數法/精度截斷 | 配置 ToPlainStringSerializer |
public class MoneyUtils {
private static final MathContext MC = new MathContext(28, RoundingMode.HALF_UP); // DECIMAL128 精度
private static final int SCALE = 2; // 分
public static BigDecimal add(BigDecimal... vals) {
return Arrays.stream(vals).reduce(BigDecimal.ZERO, BigDecimal::add);
}
public static BigDecimal multiply(BigDecimal a, BigDecimal b) {
return a.multiply(b).setScale(SCALE, RoundingMode.HALF_UP);
}
public static BigDecimal divide(BigDecimal a, BigDecimal b) {
return a.divide(b, SCALE, RoundingMode.HALF_UP);
}
public static BigDecimal round(BigDecimal val) {
return val.setScale(SCALE, RoundingMode.HALF_UP);
}
public static int compare(BigDecimal a, BigDecimal b) {
return a.compareTo(b);
}
public static String toPlainString(BigDecimal val) {
return val.stripTrailingZeros().toPlainString(); // 去尾零,無科學計數
}
}
BigDecimal 是 Java 精確計算的基石。牢記三句口訣:
配合 MoneyUtils 封裝,財務級計算既安全又高效。