浮點數 float/double 因二進制無法精確表示十進制小數(如 0.1),導致金融、計量等領域計算誤差。BigDecimal 以「不可變、任意精度、十進制」特性成為精確計算標準工具。本文系統講解構造器選擇、運算不可變性、標度機制及常見工具方法。
BigDecimal 內部由兩部分組成:
BigInteger)。13.412 → 非標度值 13412,標度 3。12000 標度 -3 → 12000 × 103 = 12,000,000。數值 = unscaledValue × 10-scale。
new BigDecimal(1.22) // 輸出 1.2199999999999999733546474089962430298328399658203125
new BigDecimal("1.22") // 輸出 1.22
原因:double 二進制近似值直接傳入,BigDecimal 忠實保存該近似值;字符串構造器解析十進制字面量,精確無誤。
new BigDecimal(String)。double,用 BigDecimal.valueOf(double)(內部調用 Double.toString 再解析)或 new BigDecimal(String.valueOf(double))。new BigDecimal(double)。BigDecimal a = new BigDecimal("1.22");
BigDecimal b = new BigDecimal("2.22");
a.add(b); // 返回新對象 3.44,a 仍為 1.22
a = a.add(b); // 正確:接收返回值
鐵律:所有算術方法(add/subtract/multiply/divide)均返回新 BigDecimal,原對象不變。切記賦值接收!
// 無限循環小數必須指定精度與舍入模式
a.divide(b, 10, RoundingMode.HALF_UP) // 保留 10 位,四捨五入
// 或使用 MathContext
a.divide(b, new MathContext(10, RoundingMode.HALF_UP))
常用舍入模式:HALF_UP(四捨五入)、HALF_EVEN(銀行家舍入)、DOWN(截斷)、UP(遠離零)。
| 方法 | 用途 | 示例 |
|---|---|---|
stripTrailingZeros() | 移除尾部無意義零,可能改變標度 | 120.00 → 1.2E2 (標度 -1) |
setScale(scale, roundingMode) | 設置標度(四捨五入) | 1.234.setScale(2, HALF_UP) → 1.23 |
compareTo() | 數值比較(不可用 equals,它比較標度) | new BigDecimal("1.0").compareTo("1.00") == 0 |
signum() | 符號:-1/0/1 | |
scale() | 獲取標度 |
private boolean isIntegerValue(BigDecimal bd) {
return bd.signum() == 0
|| bd.scale() <= 0
|| bd.stripTrailingZeros().scale() <= 0;
}
邏輯:
1. 值為 0 → 整數。
2. 標度 ≤ 0 → 無小數位或科學記數法表示整數。
3. 去尾部零後標度 ≤ 0 → 如 10.00 去零後 1E1 標度 -1,屬整數。
static final BigDecimal ZERO = BigDecimal.ZERO 等,避免重複創建。long 定點數(乘 100/10000 存分/厘),但維護成本高。BigDecimal 實現 Serializable,但建議存儲為字符串或 scale + unscaledValue 兩字段,避免跨版本反序列化問題。stripTrailingZeros 是判斷整數關鍵。ArithmeticException。掌握 BigDecimal,讓金額計算告別「少一分、多一分」的尷尬。