首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

BigDecimal 除法拋出 ArithmeticException:非終結十進制展開異常的根因與三種解決方案

     編輯:Java 相關知識

使用 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 設計強制要求調用者顯式指定精度與舍入模式。

二、 三種標準解決方案

方案 1:指定精度與舍入模式(最常用)

// 保留 2 位小數,四捨五入
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP);
// 結果:0.33

// 保留 10 位小數,向下取整
BigDecimal result2 = a.divide(b, 10, RoundingMode.DOWN);
// 結果:0.3333333333

方案 2:使用 MathContext 統一精度控制

// 精度 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。

方案 3:精確除法(僅當結果有限位數時)

// 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); // 使用無參重載
    }
}

五、 避坑清單

  1. 絕不使用 double/float 進行金融計算:二進制浮點數無法精確表示 0.1 等十進制小數,0.1 + 0.2 = 0.30000000000000004。
  2. 構造 BigDecimal 必須用 String 構造器:new BigDecimal("0.1") 精確;new BigDecimal(0.1) 繼承 double 不精確值。
  3. 除法永遠顯式指定 scale & roundingMode:將其作為編碼規範,Code Review 重點檢查。
  4. 比較大小用 compareTo(),不要用 equals():equals 比較值且比較 scale(2.0 != 2.00),compareTo 僅比較數值大小。
  5. 保留小數位輸出用 setScale():bd.setScale(2, RoundingMode.HALF_UP),而非除法參數控制輸出格式。

六、 總結

ArithmeticException: Non-terminating decimal expansion 是 BigDecimal 守護精確計算的防線。掌握 divide(scale, roundingMode)、MathContext、divideExact 三種模式,配合統一工具類與編碼規範,即可在金融、電商、計費等高精度業務中從容應對除法運算。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved