在 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
}
}
BigDecimaljava.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 (分/厘為單位) | 整數運算極快,無對象創建開銷,需統一縮放因子 |
new BigDecimal(0.1) → 值為 0.10000000000000000555...,務必用字符串構造。divide 不指定 scale 遇無限循環小數會拋 ArithmeticException。equals 比較 2.0 與 2.00 返回 false,業務比較請用 compareTo 或 stripTrailingZeros()。