float 和 double 設計用於科學計算,採用二進制浮點運算,無法精確表示許多十進制小數(如 0.1)。商業場景(金額、利率、稅費)要求絕對精確,必須使用 BigDecimal。
// 錯誤示範
double a = 0.1;
double b = 0.2;
System.out.println(a + b); // 輸出:0.30000000000000004
// 正確做法
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b)); // 輸出:0.3
BigDecimal = unscaledValue × 10^(-scale)
| 構造方法 | 代碼示例 | 結果 | 推薦度 |
|---|---|---|---|
| double | new BigDecimal(1.22) | 1.21999999999999997335... | ❌ 不推薦 |
| String | new BigDecimal("1.22") | 1.22 (精確) | ✅ 強烈推薦 |
| valueOf(double) | BigDecimal.valueOf(1.22) | 1.22 (內部調用 Double.toString) | ✅ 可接受 |
核心原則:永遠使用 String 構造方法或 valueOf(double),絕不直接使用 double 構造器。
BigDecimal 是不可變的,所有運算方法都返回新對象:
BigDecimal a = new BigDecimal("1.22");
BigDecimal b = new BigDecimal("2.22");
// ❌ 錯誤:a 不會改變
a.add(b);
System.out.println(a); // 仍輸出 1.22
// ✅ 正確:必須接收返回值
BigDecimal result = a.add(b);
System.out.println(result); // 3.44
// 或鏈式調用
a = a.add(b).multiply(new BigDecimal("2"));
| 操作 | 方法 | 示例 |
|---|---|---|
| 加法 | add(BigDecimal) | a.add(b) |
| 減法 | subtract(BigDecimal) | a.subtract(b) |
| 乘法 | multiply(BigDecimal) | a.multiply(b) |
| 除法 | divide(BigDecimal, scale, RoundingMode) | a.divide(b, 2, RoundingMode.HALF_UP) |
| 比較 | compareTo(BigDecimal) | a.compareTo(b) // -1/0/1 |
| 絕對值 | abs() | a.abs() |
| 取整/四捨五入 | setScale(scale, RoundingMode) | a.setScale(2, RoundingMode.HALF_UP) |
// ❌ 會拋出 ArithmeticException: Non-terminating decimal expansion
BigDecimal result = a.divide(b);
// ✅ 必須指定保留小數位和舍入模式
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP); // 保留2位,四捨五入
BigDecimal result = a.divide(b, 10, RoundingMode.HALF_EVEN); // 銀行家算法
| 模式 | 行為 | 示例 (保留1位) |
|---|---|---|
| UP | 遠離零方向 | 1.11→1.2, -1.11→-1.2 |
| DOWN | 趨向零方向 | 1.19→1.1, -1.19→-1.1 |
| CEILING | 趨向正無窮 | 1.11→1.2, -1.19→-1.1 |
| FLOOR | 趨向負無窮 | 1.19→1.1, -1.11→-1.2 |
| HALF_UP | 四捨五入(傳統) | 1.15→1.2, 1.14→1.1 |
| HALF_DOWN | 五捨六入 | 1.15→1.1, 1.16→1.2 |
| HALF_EVEN | 銀行家算法 | 1.15→1.2, 1.25→1.2 |
// ❌ 錯誤:equals 比較值和 scale,1.0 != 1.00
a.equals(b);
// ✅ 正確:compareTo 只比較數值大小
int cmp = a.compareTo(b);
// cmp < 0: a < b
// cmp == 0: a == b
// cmp > 0: a > b
簡化版 add 邏輯:
public BigDecimal add(BigDecimal augend) {
// 1. 對齊小數位(scale 取大的)
// 2. 整數值相加(intCompact 或 BigInteger)
// 3. 檢查溢出
// 4. 返回 new BigDecimal(sum, newScale) // 新對象!
}
這就是為什麼必須接收返回值的根本原因。
new BigDecimal("str"),次選 BigDecimal.valueOf(double)compareTo,不用 equalspublic static final BigDecimal ZERO = BigDecimal.ZERO;
public static final BigDecimal ONE = BigDecimal.ONE;
public static final BigDecimal HUNDRED = new BigDecimal("100");
DECIMAL 類型字段直接用 BigDecimallong 儲存分/厘,展示時除以 100/1000MathContext 限制精度:new BigDecimal(value, MathContext.DECIMAL64)BigDecimal 是 Java 處理精確計算的利器,核心在於:String 構造、不可變接收返回值、除法指定精度、compareTo 比較。掌握這四點,即可避開 99% 的浮點數陷阱,構建可靠的金融級計算系統。