使用 BigDecimal.divide() 進行除法運算時,若結果為無限循環小數(如 1÷3=0.333...),JVM 會拋出 java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result。本文深入剖析異常根因,提供三種生產級解決方案及最佳實踐建議。
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("3");
BigDecimal result = a.divide(b); // 拋出 ArithmeticException
根因:BigDecimal 表示精確十進制數值。除法若無法在有限位數內精確表示(即非終結十進制展開),為避免隱式截斷導致業務數據失真,API 設計強制要求調用者顯式指定精度與舍入模式。
// 保留 2 位小數,四捨五入
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP);
// 結果:0.33
// 保留 10 位小數,向下取整
BigDecimal result2 = a.divide(b, 10, RoundingMode.DOWN);
// 結果:0.3333333333
// 精度 10 位,四捨五入
MathContext mc = new MathContext(10, RoundingMode.HALF_UP);
BigDecimal result = a.divide(b, mc);
// 結果:0.3333333333
注意:
MathContext的精度是有效數字總位數(含整數部分),非小數位數。如new MathContext(3)對 123.456 → 123,對 0.123456 → 0.123。
// 10 ÷ 2 = 5,精確可表示
BigDecimal exact = new BigDecimal("10").divide(new BigDecimal("2"));
// 結果:5,不拋異常
若不能精確表示,仍拋異常。適用於已知被除數是除數整數倍的業務場景(如金額拆分、換算)。
HALF_UP | 四捨五入(≥5 進位) | **金融金額標準**,最常用 |
| HALF_DOWN | 五捨六入(>5 進位) | 統計學、科學計算 |
| HALF_EVEN | 銀行家舍入(5 向偶數進位) | 減少長序列累積偏差,IEEE 754 默認 |
| DOWN | 截斷向零方向 | 向下取整(正數)、向上取整(負數) |
| UP | 遠離零方向 | 向上取整(正數) |
| CEILING | 向正無窮大 | 數學天花板函數 |
| FLOOR | 向負無窮大 | 數學地板函數 |
| UNNECESSARY | 要求精確,否則拋異常 | 方案 3 內部使用 |
public final class BigDecimalUtils {
private static final int DEFAULT_SCALE = 10;
private static final RoundingMode DEFAULT_ROUNDING = RoundingMode.HALF_UP;
private BigDecimalUtils() {}
/** 安全除法:默認 10 位小數,四捨五入 */
public static BigDecimal divide(BigDecimal dividend, BigDecimal divisor) {
return divide(dividend, divisor, DEFAULT_SCALE, DEFAULT_ROUNDING);
}
/** 安全除法:指定小數位數與舍入模式 */
public static BigDecimal divide(BigDecimal dividend, BigDecimal divisor, int scale, RoundingMode roundingMode) {
if (divisor == null || divisor.compareTo(BigDecimal.ZERO) == 0) {
throw new ArithmeticException("除數不能為零");
}
return dividend.divide(divisor, scale, roundingMode);
}
/** 精確除法:結果必須有限位數,否則拋異常 */
public static BigDecimal divideExact(BigDecimal dividend, BigDecimal divisor) {
return dividend.divide(divisor); // 使用無參重載
}
}
0.1 + 0.2 = 0.30000000000000004。new BigDecimal("0.1") 精確;new BigDecimal(0.1) 繼承 double 不精確值。equals 比較值且比較 scale(2.0 != 2.00),compareTo 僅比較數值大小。bd.setScale(2, RoundingMode.HALF_UP),而非除法參數控制輸出格式。ArithmeticException: Non-terminating decimal expansion 是 BigDecimal 守護精確計算的防線。掌握 divide(scale, roundingMode)、MathContext、divideExact 三種模式,配合統一工具類與編碼規範,即可在金融、電商、計費等高精度業務中從容應對除法運算。