在Java中 0.1 + 0.2 不等于0.3 ?
前言
在Java中浮點(diǎn)數(shù)的精度問題,是一個讓人非常頭疼的問題。
在文章開頭,大家一起先看看下面這個非常經(jīng)典錯誤案例:
double total = 0.1 + 0.2;
System.out.println(total); // 0.30000000000000004
System.out.println(total == 0.3); // false!計(jì)算兩個double類型的數(shù)字,0.1+0.2不等于0.3,其結(jié)果卻是:0.30000000000000004。
浮點(diǎn)數(shù)精度是Java數(shù)學(xué)計(jì)算的隱形炸彈。
今天這篇文章跟大家一起聊聊,浮點(diǎn)數(shù)的精度問題,希望對你會有所幫助。
1.計(jì)算機(jī)的"數(shù)學(xué)缺陷"
1.1 IEEE 754標(biāo)準(zhǔn)的本質(zhì)
浮點(diǎn)數(shù)內(nèi)存結(jié)構(gòu):
圖片
64位的浮點(diǎn)數(shù)是由:
- 1位符號位
- 11位指數(shù)位
- 52位尾數(shù)位
組成的。
0.1的二進(jìn)制真相:
猜猜0.1轉(zhuǎn)換成二進(jìn)制的結(jié)果是什么?
System.out.println(Long.toBinaryString(Double.doubleToLongBits(0.1)));輸出結(jié)果:11111110111001100110011001100110011001100110011001100110011010
浮點(diǎn)數(shù)的內(nèi)部是由很多個0和1組成的。
無限循環(huán)小數(shù):

1.2 精度丟失的數(shù)學(xué)原理
誤差產(chǎn)生公式:

Java驗(yàn)證代碼:
System.out.println(0.1 + 0.2);打印結(jié)果:0.30000000000000004
System.out.println(0.1 * 0.2);打印結(jié)果:0.020000000000000004
2.精度問題的重災(zāi)區(qū)
2.1 金融計(jì)算的致命誤差
錯誤案例:
電商訂單金額計(jì)算:
double price = 19.99;
double quantity = 3;
double total = price * quantity;total的結(jié)果是:59.970000000000006
銀行利息計(jì)算:
double rate = 0.05;
double interest = 10000 * rate / 365;每日利息誤差的結(jié)果是:1.36986301369863
如果每一個用戶的資金都有誤差,誤差日積月累,總的誤差值會很大。
2.2 科學(xué)計(jì)算的精度災(zāi)難
典型場景:
地理坐標(biāo)計(jì)算:
double lat1 = 39.90923;
double lng1 = 116.397428;
double distance = calculateDistance(lat1, lng1, lat2, lng2);誤差可達(dá)米級。
工業(yè)控制系統(tǒng):
double sensorValue = 0.000001;
double adjusted = sensorValue * 1000000;放大后誤差顯著。
既然這么多業(yè)務(wù)場景,都不允許出現(xiàn)浮點(diǎn)數(shù)精度問題。
那么,我們該如何解決精度問題呢?
3 解決方案
3.1 BigDecimal的正確使用
我們都知道BigDecimal能夠解決浮點(diǎn)數(shù)的精度問題。
但如果用錯了,也會出現(xiàn)問題。
錯誤用法:
BigDecimal d1 = new BigDecimal(0.1);這里使用了使用double構(gòu)造創(chuàng)建BigDecimal對象,會導(dǎo)致精度丟失。
正確用法:
BigDecimal d2 = new BigDecimal("0.1");
BigDecimal d3 = new BigDecimal("0.2");
// 精確計(jì)算
BigDecimal sum = d2.add(d3); // 0.3使用字符串構(gòu)造,會發(fā)現(xiàn)精度計(jì)算正確。
四則運(yùn)算規(guī)范:
除法必須指定精度和舍入模式:
BigDecimal result = a.divide(b, 10, RoundingMode.HALF_UP);3.2 整數(shù)運(yùn)算的藝術(shù)
貨幣計(jì)算最佳實(shí)踐:
以分為單位存儲金額:
long priceInCents = 1999; // 19.99元
long quantity = 3;
long totalCents = priceInCents * quantity; // 5997分 = 59.97元3.3 精度容忍比較
浮點(diǎn)數(shù)等值判斷:
錯誤方式:
if (a == b) { ... }直接使用等于號判斷兩個浮點(diǎn)數(shù)的大小是否相等。
正確方式:
final double EPSILON = 1e-10;
if (Math.abs(a - b) < EPSILON) { ... }使用Math.abs函數(shù)判斷,兩個浮點(diǎn)數(shù)的誤差是否在可接受的范圍內(nèi)。
工業(yè)級比較工具:
import org.apache.commons.math3.util.Precision;
Precision.equals(0.1 + 0.2, 0.3, 1e-15); // true4.避坑指南
浮點(diǎn)數(shù)使用決策樹
圖片
精度保障檢查清單
- 禁止使用float/double表示貨幣
- BigDecimal必須用String構(gòu)造
- 除法操作指定RoundingMode
- 浮點(diǎn)數(shù)比較使用誤差范圍
- 復(fù)雜運(yùn)算前進(jìn)行精度測試
三條黃金法則:
- 構(gòu)造嚴(yán)謹(jǐn):BigDecimal必須字符串初始化
- 單位轉(zhuǎn)換:金額以最小單位存儲
- 誤差可控:科學(xué)計(jì)算明確精度范圍


























