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

Java BigDecimal 精確運算完整指南

     編輯:Java 相關知識

Java BigDecimal 高精度商業運算實戰

浮點數 float/double 遵循 IEEE 754 標準,無法精確表示十進制小數 (如 0.1),導致商業運算 (金額、利率、稅費) 出現誤差。java.math.BigDecimal 提供任意精度有理數運算,是金融、電商、計費系統的標準選擇。本文系統講解建構器陷阱、核心運算、比較、格式化、效能優化與最佳實踐。

建構器陷阱:double vs String


BigDecimal d1 = new BigDecimal(0.1); 
// 實際值: 0.1000000000000000055511151231257827021181583404541015625

BigDecimal d2 = new BigDecimal("0.1"); 
// 精確值: 0.1
  • double 建構器:將 double 的二進制近似值直接轉換,引入不可預測誤差
  • String 建構器:解析十進制字串,精確無誤,**強烈推薦**
  • valueOf 工廠方法:BigDecimal.valueOf(double) 內部用 Double.toString() 轉字串再解析,相對安全

核心運算 API

運算方法說明
加法add(BigDecimal)回傳新物件,不可變
減法subtract(BigDecimal)
乘法multiply(BigDecimal)
除法divide(BigDecimal, scale, roundingMode)必須指定精度與捨入模式,否則無限循環小數拋 ArithmeticException
次方pow(int)冪次運算
開根號sqrt(MathContext)JDK 9+

除法精度控制關鍵


// 錯誤:10 / 3 無限循環小數 → ArithmeticException
BigDecimal wrong = BigDecimal.TEN.divide(BigDecimal.valueOf(3));

// 正確:指定保留 2 位小數,四捨五入
BigDecimal correct = BigDecimal.TEN.divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP);
// 結果: 3.33

// 推薦:使用 MathContext 統一精度上下文
MathContext mc = new MathContext(10, RoundingMode.HALF_UP); // 10 位有效數字
BigDecimal result = BigDecimal.TEN.divide(BigDecimal.valueOf(3), mc);
// 結果: 3.333333333

比較與等值判斷


BigDecimal a = new BigDecimal("1.00");
BigDecimal b = new BigDecimal("1.0");

// compareTo: 比較數值大小,忽略尺度 (scale)
a.compareTo(b); // 0 (相等)

// equals: 比較數值 + 尺度 (1.00 vs 1.0 → false)
a.equals(b); // false

// 實務建議:永遠用 compareTo 判斷數值相等
if (a.compareTo(b) == 0) { ... }

型別轉換

來源目標方法注意事項
int/longBigDecimalnew BigDecimal(val) / valueOf(val)精確轉換
float/doubleBigDecimalBigDecimal.valueOf(val)避免 new BigDecimal(double)
StringBigDecimalnew BigDecimal(str)可能拋 NumberFormatException
BigDecimalint/longintValue() / longValue()截斷小數部份,不四捨五入
BigDecimalfloat/doublefloatValue() / doubleValue()可能損失精度
BigDecimalStringtoString() / toPlainString()toPlainString 無科學記號

實戰範例:李白買酒問題 (逆向運算)


/*
  李白遇店酒量翻倍,遇花飲一斗,五次後喝光。
  逆向推導:第5次飲酒前有1斗 → 遇店前0.5斗 → 第4次飲酒前1.5斗 → 遇店前0.75斗...
*/
import java.math.*;

public class LiBaiWine {
    public static void main(String[] args) {
        BigDecimal volume = BigDecimal.ZERO; // 最後喝光 = 0
        BigDecimal oneDou = BigDecimal.ONE;  // 1斗
        BigDecimal two = BigDecimal.valueOf(2);

        for (int i = 0; i < 5; i++) {
            // 逆向:先加回飲掉的1斗,再除以2 (遇店翻倍的逆運算)
            volume = volume.add(oneDou).divide(two, 10, RoundingMode.HALF_UP);
        }
        System.out.println("初始酒量: " + volume + " 斗");
        // 輸出: 0.96875 斗
    }
}

金融運算最佳實踐清單

  1. 只用 String/valueOf 建構:禁用 new BigDecimal(double)
  2. 除法必指定精度與捨入:divide(divisor, scale, RoundingMode.HALF_UP)
  3. 統一使用 MathContext:全域精度策略,避免各處硬編碼
  4. 比較用 compareTo:勿用 equals
  5. 貨幣單位用最小單位 (分/厘):儲存長整數,顯示時除以 100,完全避免小數點問題
  6. 不可變性:所有運算回傳新物件,鏈式調用 a.add(b).multiply(c)
  7. 序列化:實作 Serializable,但建議儲存為字串或長整數 (分)
  8. 資料庫映射:MySQL DECIMAL(19,4)、PostgreSQL NUMERIC、JPA @Column(precision=19, scale=4)

效能優化提示

  • 重用 MathContext、RoundingMode 常數
  • 避免循環中重複建構相同常數 (預先定義 BigDecimal ONE = BigDecimal.ONE)
  • 大量運算考慮 double 近似計算 + 關鍵步驟 BigDecimal 校驗
  • 極高效能需求可用 JBigDecimal 或原生 long (分為單位)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved