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

Java BigDecimal 精確計算完全指南:避開浮點數陷阱、構造器選擇、運算不可變性

     編輯:Java 相關知識

為什麼需要 BigDecimal?

float/double 遵循 IEEE 754 二進制浮點數,**無法精確表示十進制小數**(如 0.1、1.22)。財務、電商、計量等精度敏感場景**必須使用 BigDecimal**。

構造器選擇:決定性能與精度的分水嶺

構造器示例結果推薦度
new BigDecimal(double)new BigDecimal(1.22)1.21999999999999997335...❌ 禁用
new BigDecimal(String)new BigDecimal("1.22")1.22 (精確)✅ 首選
BigDecimal.valueOf(double)BigDecimal.valueOf(1.22)1.22 (經過 Double.toString)✅ 兼容 double 來源
new BigDecimal(char[])new BigDecimal(new char[]{'1','.','2','2'})1.22⭐ 高性能場景

核心原則:字符串構造最精確,double 必經 valueOf()。

不可變性:每次運算產生新對象

BigDecimal a = new BigDecimal("1.22");
BigDecimal b = new BigDecimal("2.22");
a.add(b);           // 返回新對象 3.44,a 仍為 1.22
BigDecimal c = a.add(b); // 正確:接收返回值

同理 subtract/multiply/divide 皆返回新對象,鏈式調用:a.add(b).multiply(c)。

除法與精度控制:必須指定精度與舍入模式

// ❌ 無限循環小數拋 ArithmeticException
BigDecimal d = a.divide(b); 

// ✅ 指定刻度與舍入
BigDecimal d = a.divide(b, 10, RoundingMode.HALF_UP); // 保留 10 位,四捨五入
BigDecimal d2 = a.divide(b, MathContext.DECIMAL128);  // 用 MathContext 統一精度上下文

常用舍入模式

  • HALF_UP:四捨五入 (最常用)
  • HALF_DOWN:五捨六入
  • HALF_EVEN:銀行家舍入 (縮減統計偏差)
  • DOWN:截斷 (向零)
  • UP:遠離零
  • CEILING/FLOOR:向正/負無窮

比較大小:compareTo 而非 equals

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

排序、去重、Map Key 必用 compareTo。

格式化輸出

BigDecimal amt = new BigDecimal("1234.5");
// 保留 2 位,分組分隔符,貨幣符號
NumberFormat fmt = NumberFormat.getCurrencyInstance(Locale.CHINA);
fmt.setMaximumFractionDigits(2);
fmt.setMinimumFractionDigits(2);
System.out.println(fmt.format(amt)); // ¥1,234.50

// 科學計數
System.out.println(amt.toEngineeringString()); // 1.2345E3

性能優化技巧

  1. 復用常量:static final BigDecimal ZERO = BigDecimal.ZERO;
  2. 避免循環中創建:預先構造常用值
  3. 大批量計算:考慮 long 整數分/厘存儲,只在入口/出口轉 BigDecimal
  4. MathContext 共享:統一精度上下文

常見坑與避坑

坑代碼後果正確
double 構造new BigDecimal(0.1)0.10000000000000000555new BigDecimal("0.1") / valueOf(0.1)
忽略返回值a.add(b);a 不變,邏輯錯誤a = a.add(b)
除法不指定精度a.divide(b)ArithmeticExceptiona.divide(b, scale, mode)
equals 比較x.equals(y)scale 不同返回 falsex.compareTo(y) == 0
序列化精度丟失JSON 默認序列化科學計數法/精度截斷配置 ToPlainStringSerializer

實戰模板:金額計算工具類

public class MoneyUtils {
    private static final MathContext MC = new MathContext(28, RoundingMode.HALF_UP); // DECIMAL128 精度
    private static final int SCALE = 2; // 分

    public static BigDecimal add(BigDecimal... vals) {
        return Arrays.stream(vals).reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    public static BigDecimal multiply(BigDecimal a, BigDecimal b) {
        return a.multiply(b).setScale(SCALE, RoundingMode.HALF_UP);
    }
    public static BigDecimal divide(BigDecimal a, BigDecimal b) {
        return a.divide(b, SCALE, RoundingMode.HALF_UP);
    }
    public static BigDecimal round(BigDecimal val) {
        return val.setScale(SCALE, RoundingMode.HALF_UP);
    }
    public static int compare(BigDecimal a, BigDecimal b) {
        return a.compareTo(b);
    }
    public static String toPlainString(BigDecimal val) {
        return val.stripTrailingZeros().toPlainString(); // 去尾零,無科學計數
    }
}

總結

BigDecimal 是 Java 精確計算的基石。牢記三句口訣:

  1. 構造用字符串,double 用 valueOf
  2. 運算必接收返回值,對象不可變
  3. 除法必指定精度舍入,比較用 compareTo

配合 MoneyUtils 封裝,財務級計算既安全又高效。

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