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

Java 浮點數精度問題徹底解決:BigDecimal 最佳實踐指南

     編輯:Java 相關知識

問題現場

double result = 4.015 * 100;
System.out.println(result);  // 401.49999999999994 ❌
// 預期:401.5

根因:IEEE 754 二進制表示缺陷

十進制 0.1、0.4 等無法用有限二進制精確表示:

0.4 (十進制) = 0.011001100110011... (二進制無限循環)
double 存儲:符號位1 + 指數11 + 尾數52 → 截斷誤差不可避免

解決方案:BigDecimal 任意精度運算

核心原則

商業計算必須用 BigDecimal;科學/工程計算可用 float/double。 —— Effective Java

構造器陷阱與正確用法

構造方式代碼結果評價
double 值構造new BigDecimal(4.015)4.014999999999999...❌ 已含誤差
字符串構造new BigDecimal("4.015")4.015✅ 精確
valueOf 工廠BigDecimal.valueOf(4.015)4.015✅ 推薦(內部用 toString)

四則運算 API

BigDecimal a = new BigDecimal("4.015");
BigDecimal b = new BigDecimal("100");

BigDecimal add = a.add(b);           // 加法
BigDecimal sub = a.subtract(b);      // 減法
BigDecimal mul = a.multiply(b);      // 乘法
BigDecimal div = a.divide(b, 2, RoundingMode.HALF_UP);  // 除法(必須指定精度與舍入模式)

格式化輸出

NumberFormat fmt = NumberFormat.getCurrencyInstance();
fmt.setMaximumFractionDigits(2);
System.out.println(fmt.format(mul));  // ¥401.50

BigDecimal 內部原理剖析

// 核心結構
public class BigDecimal extends Number implements Comparable<BigDecimal> {
    private final BigInteger intVal;  // 無標度值(任意大整數)
    private final int scale;          // 標度(小數點右移位數)
    
    // 表示值 = intVal × 10^(-scale)
    // 例:4.015 → intVal=4015, scale=3
}
  • BigInteger:int[] 數組實現任意大整數
  • scale:int 類型,支持負數(科學計數法)
  • 不可變性:每次運算產生新對象,線程安全但有性能開銷

性能優化策略

  1. 復用對象:避免循環中重複創建
  2. 批量運算:合併表達式減少中間對象
  3. 精度控制:除法指定合理 scale,避免無限循環小數
  4. 熱點路徑:極端性能敏感場景考慮 long 整數存儲(分為單位)

完整工具類封裝

public class DecimalUtil {
    private static final int DEF_SCALE = 10;
    private static final RoundingMode DEF_ROUND = RoundingMode.HALF_UP;
    
    public static BigDecimal add(String a, String b) {
        return new BigDecimal(a).add(new BigDecimal(b));
    }
    
    public static BigDecimal mul(String a, String b) {
        return new BigDecimal(a).multiply(new BigDecimal(b));
    }
    
    public static BigDecimal div(String a, String b) {
        return div(a, b, DEF_SCALE);
    }
    
    public static BigDecimal div(String a, String b, int scale) {
        return new BigDecimal(a).divide(new BigDecimal(b), scale, DEF_ROUND);
    }
    
    public static String toPlainString(BigDecimal bd) {
        return bd.stripTrailingZeros().toPlainString();  // 去尾部零
    }
}

常見坑點避雷

  1. 除法不指定精度:1/3 拋 ArithmeticException
  2. equals 比較值+標度:2.0.equals(2.00) 為 false,用 compareTo() == 0
  3. double 參與運算:污染精度,全鏈路用 String/BigDecimal
  4. 序列化:首選 toPlainString() 避免科學計數法

總結

浮點數精度問題源於二進制表示本質缺陷。BigDecimal + 字符串構造 + 顯式精度控制 是商業系統的鐵律。封裝工具類統一入口,代碼審查攔截 double 參與金額運算,從源頭杜絕精度 Bug。

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