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

Java BigDecimal 精確計算完全指南:避開 double 陷阱、掌握四捨五入模式與除法陷阱

     編輯:Java 相關知識

為什麼不能用 double/float 做金融計算?


System.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(1.03 - 0.42); // 0.6100000000000001
System.out.println(190.00 * 100); // 18999.999999999996

IEEE 754 浮點數無法精確表示十進制小數(如 0.1 二進制無限循環),導致財務系統不可接受的精度損失。金額、利率、匯率、庫存計量必須用 BigDecimal。

BigDecimal 構造陷阱:String vs double


BigDecimal a = new BigDecimal("0.1");  // 精確 0.1
BigDecimal b = new BigDecimal(0.1);    // 0.1000000000000000055511151231257827021181583404541015625
System.out.println(a); // 0.1
System.out.println(b); // 0.10000000000000000555...

鐵律:只用 String 構造,或用 BigDecimal.valueOf(double)(內部調用 Double.toString() 獲得標準表示)。

四則運算:不可變模式


BigDecimal x = new BigDecimal("12.34");
BigDecimal y = new BigDecimal("5.67");

BigDecimal sum = x.add(y);           // 18.01
BigDecimal diff = x.subtract(y);     // 6.67
BigDecimal prod = x.multiply(y);     // 69.9678
BigDecimal quot = x.divide(y, 4, RoundingMode.HALF_UP); // 2.1764

所有運算返回新對象,原對象不變,支持鏈式調用:a.add(b).multiply(c)。

除法與四捨五入:核心難點

1. 必須指定精度與舍入模式


// 除不盡拋 ArithmeticException: Non-terminating decimal expansion
BigDecimal d = BigDecimal.ONE.divide(BigDecimal.valueOf(3)); // 崩潰!

// 正確:指定保留小數位 + 舍入模式
BigDecimal d = BigDecimal.ONE.divide(BigDecimal.valueOf(3), 4, RoundingMode.HALF_UP); // 0.3333

2. 八種 RoundingMode 對照表

模式行為示例 (保留 1 位)
HALF_UP四捨五入,5 向上1.5→2, 1.4→1, -1.5→-2
HALF_DOWN五捨六入,5 向下1.5→1, 1.6→2, -1.5→-1
HALF_EVEN銀行家算法,5 向偶數1.5→2, 2.5→2, -1.5→-2
UP遠離零方向1.1→2, -1.1→-2
DOWN趨向零方向(截斷)1.9→1, -1.9→-1
CEILING向正無窮1.1→2, -1.9→-1
FLOOR向負無窮1.9→1, -1.1→-2
UNNECESSARY精確除盡否則異常1/2 拋異常

金融標準:中國會計準則/大多數業務採用 HALF_UP;統計學/銀行結算常用 HALF_EVEN 減少累積偏差。

3. 隱蔽陷阱:HALF_DOWN 的「連續進位」現象


BigDecimal v = new BigDecimal("67.75");
System.out.println(v.divide(BigDecimal.valueOf(5), 1, RoundingMode.HALF_DOWN)); // 13.5
System.out.println(v.divide(BigDecimal.valueOf(5), 4, RoundingMode.HALF_DOWN)); // 13.5500

BigDecimal v2 = new BigDecimal("67.751");
System.out.println(v2.divide(BigDecimal.valueOf(5), 1, RoundingMode.HALF_DOWN)); // 13.6  <-- 陷阱!
System.out.println(v2.divide(BigDecimal.valueOf(5), 4, RoundingMode.HALF_DOWN)); // 13.5502

原因:HALF_DOWN 比較的是「捨去位之後的所有數字」與 0.5 的大小。
13.5502 捨去小數點後 1 位 → 看 0.0502 > 0.05 → 進位 → 13.6。
建議:金融業務統一用 HALF_UP,語義直觀,無此陷阱。

比較大小:compareTo vs equals


BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");
a.equals(b);        // false (scale 不同:1 vs 2)
a.compareTo(b);     // 0 (數值相等)
a.stripTrailingZeros().equals(b.stripTrailingZeros()); // true

鐵律:比較數值大小只用 compareTo;equals 同時比較數值與標度。

格式化輸出:去尾零、科學計數法轉換


BigDecimal n = new BigDecimal("123.4500");
n.stripTrailingZeros().toPlainString(); // "123.45" (去尾零,非科學計數法)
n.toPlainString();                      // "123.4500" (保留標度)
n.toEngineeringString();                // "123.45E0" (工程記數法)

性能優化:避免頻繁創建對象

  • 常量預定義:static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
  • 複用 MathContext:MathContext mc = new MathContext(10, RoundingMode.HALF_UP);
  • 熱點路徑考慮 long 分/厘存儲(整數運算),落地時除 100/1000 轉 BigDecimal。

總結:BigDecimal 使用檢查清單

  1. ✅ 構造用 new BigDecimal(String) 或 BigDecimal.valueOf(double);
  2. ✅ 除法必須指定 scale + RoundingMode;
  3. ✅ 金融業務優先 RoundingMode.HALF_UP;
  4. ✅ 比較用 compareTo,不用 equals;
  5. ✅ 輸出用 stripTrailingZeros().toPlainString();
  6. ✅ 常量靜態化,熱點路徑考慮整數分存儲。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved