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

Java 浮點數精度陷阱:為什麼 2.0 - 1.1 ≠ 0.9 及解決方案

     編輯:Java 相關知識

在 Java 中進行貨幣或高精度計算時,直接使用 float 或 double 是典型的「坑」。經典案例:System.out.println(2.0 - 1.1) 輸出 0.8999999999999999 而非預期的 0.9。本文解析二進制浮點數表示原理,並推薦使用 BigDecimal 實現精確計算。

一、根本原因:二進制無法精確表示十進制小數

IEEE 754 標準下,double 佔 64 位(1 符號位 + 11 指數位 + 52 尾數位)。十進制的 0.1(即 1/10)在二進制中是無限循環小數:

0.1₁₀ = 0.0001100110011...₂ (1001 無限循環)

類似於十進制中 1/3 = 0.333... 無法精確表示,二進制無法精確存儲 1/10。因此 1.1 實際存儲為最接近的近似值,運算時誤差累積並放大。

二、錯誤示範

public class FloatPitfall {
    public static void main(String[] args) {
        System.out.println(2.0 - 1.1);           // 0.8999999999999999
        System.out.println(0.05 + 0.01);         // 0.060000000000000005
        System.out.println(1.0 - 0.42);          // 0.5800000000000001
        System.out.println(195.82 * 100);        // 19581.999999999996
    }
}

三、正確解決方案:BigDecimal

java.math.BigDecimal 以任意精度整數 + 縮放因子表示十進制數,完全避免二進制近似誤差。核心用法:

  • 創建:優先使用 BigDecimal(String) 或 BigDecimal.valueOf(double),絕不使用 new BigDecimal(double)(會攜帶 double 的近似誤差)。
  • 運算:調用 add、subtract、multiply、divide 等方法,鏈式調用返回新對象。
  • 除法保留精度:divide(divisor, scale, RoundingMode.HALF_UP) 指定小數位與四捨五入模式。
  • 比較:使用 compareTo 而非 equals(後者比較值與標度)。
import java.math.BigDecimal;
import java.math.RoundingMode;

public class PreciseCalculation {
    public static void main(String[] args) {
        // 正確創建
        BigDecimal a = new BigDecimal("2.0");
        BigDecimal b = BigDecimal.valueOf(1.1);   // 等價於 new BigDecimal("1.1")
        
        System.out.println(a.subtract(b));                    // 0.9
        System.out.println(BigDecimal.valueOf(0.05).add(BigDecimal.valueOf(0.01))); // 0.06
        
        // 除法:保留 2 位小數,四捨五入
        BigDecimal rate = BigDecimal.valueOf(100).divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP);
        System.out.println(rate); // 33.33
        
        // 貨幣計算最佳實踐:封裝工具類
        System.out.println(MoneyUtil.add("195.82", "0.01")); // 195.83
    }
}

class MoneyUtil {
    private static final int SCALE = 2;
    private static final RoundingMode MODE = RoundingMode.HALF_UP;

    public static String add(String v1, String v2) {
        return new BigDecimal(v1).add(new BigDecimal(v2)).setScale(SCALE, MODE).toString();
    }
    // subtract, multiply, divide 同理...
}

四、性能與場景建議

場景推薦類型理由
科學計算、圖形渲染、近似值可接受double / float硬件加速,速度快,內存佔用小
貨幣、金融、稅費、庫存扣減、精度敏感BigDecimal精確十進制,避免資損糾紛
高併發熱點路徑(如交易核心)long (分/厘為單位)整數運算極快,無對象創建開銷,需統一縮放因子

五、常見誤區提醒

  1. new BigDecimal(0.1) → 值為 0.10000000000000000555...,務必用字符串構造。
  2. divide 不指定 scale 遇無限循環小數會拋 ArithmeticException。
  3. equals 比較 2.0 與 2.00 返回 false,業務比較請用 compareTo 或 stripTrailingZeros()。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved