在Java中 0.1 + 0.2 不等于0.3 ?
前言
在Java中浮點數的精度問題,是一個讓人非常頭疼的問題。
在文章開頭,大家一起先看看下面這個非常經典錯誤案例:
double total = 0.1 + 0.2;
System.out.println(total); // 0.30000000000000004
System.out.println(total == 0.3); // false!
計算兩個double類型的數字,0.1+0.2不等于0.3,其結果卻是:0.30000000000000004。
浮點數精度是Java數學計算的隱形炸彈。
今天這篇文章跟大家一起聊聊,浮點數的精度問題,希望對你會有所幫助。
1.計算機的"數學缺陷"
1.1 IEEE 754標準的本質
浮點數內存結構:
圖片
64位的浮點數是由:
- 1位符號位
- 11位指數位
- 52位尾數位
組成的。
0.1的二進制真相:
猜猜0.1轉換成二進制的結果是什么?
System.out.println(Long.toBinaryString(Double.doubleToLongBits(0.1)));
輸出結果:11111110111001100110011001100110011001100110011001100110011010
浮點數的內部是由很多個0和1組成的。
無限循環小數:
1.2 精度丟失的數學原理
誤差產生公式:
Java驗證代碼:
System.out.println(0.1 + 0.2);
打印結果:0.30000000000000004
System.out.println(0.1 * 0.2);
打印結果:0.020000000000000004
2.精度問題的重災區
2.1 金融計算的致命誤差
錯誤案例:
電商訂單金額計算:
double price = 19.99;
double quantity = 3;
double total = price * quantity;
total的結果是:59.970000000000006
銀行利息計算:
double rate = 0.05;
double interest = 10000 * rate / 365;
每日利息誤差的結果是:1.36986301369863
如果每一個用戶的資金都有誤差,誤差日積月累,總的誤差值會很大。
2.2 科學計算的精度災難
典型場景:
地理坐標計算:
double lat1 = 39.90923;
double lng1 = 116.397428;
double distance = calculateDistance(lat1, lng1, lat2, lng2);
誤差可達米級。
工業控制系統:
double sensorValue = 0.000001;
double adjusted = sensorValue * 1000000;
放大后誤差顯著。
既然這么多業務場景,都不允許出現浮點數精度問題。
那么,我們該如何解決精度問題呢?
3 解決方案
3.1 BigDecimal的正確使用
我們都知道BigDecimal能夠解決浮點數的精度問題。
但如果用錯了,也會出現問題。
錯誤用法:
BigDecimal d1 = new BigDecimal(0.1);
這里使用了使用double構造創建BigDecimal對象,會導致精度丟失。
正確用法:
BigDecimal d2 = new BigDecimal("0.1");
BigDecimal d3 = new BigDecimal("0.2");
// 精確計算
BigDecimal sum = d2.add(d3); // 0.3
使用字符串構造,會發現精度計算正確。
四則運算規范:
除法必須指定精度和舍入模式:
BigDecimal result = a.divide(b, 10, RoundingMode.HALF_UP);
3.2 整數運算的藝術
貨幣計算最佳實踐:
以分為單位存儲金額:
long priceInCents = 1999; // 19.99元
long quantity = 3;
long totalCents = priceInCents * quantity; // 5997分 = 59.97元
3.3 精度容忍比較
浮點數等值判斷:
錯誤方式:
if (a == b) { ... }
直接使用等于號判斷兩個浮點數的大小是否相等。
正確方式:
final double EPSILON = 1e-10;
if (Math.abs(a - b) < EPSILON) { ... }
使用Math.abs函數判斷,兩個浮點數的誤差是否在可接受的范圍內。
工業級比較工具:
import org.apache.commons.math3.util.Precision;
Precision.equals(0.1 + 0.2, 0.3, 1e-15); // true
4.避坑指南
浮點數使用決策樹
圖片
精度保障檢查清單
- 禁止使用float/double表示貨幣
- BigDecimal必須用String構造
- 除法操作指定RoundingMode
- 浮點數比較使用誤差范圍
- 復雜運算前進行精度測試
三條黃金法則:
- 構造嚴謹:BigDecimal必須字符串初始化
- 單位轉換:金額以最小單位存儲
- 誤差可控:科學計算明確精度范圍