在Java中 0.1 + 0.2 不等于0.3 ?
前言
在Java中浮點(diǎn)數(shù)的精度問題,是一個(gè)讓人非常頭疼的問題。
在文章開頭,大家一起先看看下面這個(gè)非常經(jīng)典錯(cuò)誤案例:
double total = 0.1 + 0.2;
System.out.println(total); // 0.30000000000000004
System.out.println(total == 0.3); // false!
計(jì)算兩個(gè)double類型的數(shù)字,0.1+0.2不等于0.3,其結(jié)果卻是:0.30000000000000004。
浮點(diǎn)數(shù)精度是Java數(shù)學(xué)計(jì)算的隱形炸彈。
今天這篇文章跟大家一起聊聊,浮點(diǎn)數(shù)的精度問題,希望對(duì)你會(huì)有所幫助。
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位符號(hào)位
- 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)部是由很多個(gè)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ì)算的致命誤差
錯(cuò)誤案例:
電商訂單金額計(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
如果每一個(gè)用戶的資金都有誤差,誤差日積月累,總的誤差值會(huì)很大。
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á)米級(jí)。
工業(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ù)的精度問題。
但如果用錯(cuò)了,也會(huì)出現(xiàn)問題。
錯(cuò)誤用法:
BigDecimal d1 = new BigDecimal(0.1);
這里使用了使用double構(gòu)造創(chuàng)建BigDecimal對(duì)象,會(huì)導(dǎo)致精度丟失。
正確用法:
BigDecimal d2 = new BigDecimal("0.1");
BigDecimal d3 = new BigDecimal("0.2");
// 精確計(jì)算
BigDecimal sum = d2.add(d3); // 0.3
使用字符串構(gòu)造,會(huì)發(fā)現(xiàn)精度計(jì)算正確。
四則運(yùn)算規(guī)范:
除法必須指定精度和舍入模式:
BigDecimal result = a.divide(b, 10, RoundingMode.HALF_UP);
3.2 整數(shù)運(yùn)算的藝術(shù)
貨幣計(jì)算最佳實(shí)踐:
以分為單位存儲(chǔ)金額:
long priceInCents = 1999; // 19.99元
long quantity = 3;
long totalCents = priceInCents * quantity; // 5997分 = 59.97元
3.3 精度容忍比較
浮點(diǎn)數(shù)等值判斷:
錯(cuò)誤方式:
if (a == b) { ... }
直接使用等于號(hào)判斷兩個(gè)浮點(diǎn)數(shù)的大小是否相等。
正確方式:
final double EPSILON = 1e-10;
if (Math.abs(a - b) < EPSILON) { ... }
使用Math.abs函數(shù)判斷,兩個(gè)浮點(diǎn)數(shù)的誤差是否在可接受的范圍內(nèi)。
工業(yè)級(jí)比較工具:
import org.apache.commons.math3.util.Precision;
Precision.equals(0.1 + 0.2, 0.3, 1e-15); // true
4.避坑指南
浮點(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)換:金額以最小單位存儲(chǔ)
- 誤差可控:科學(xué)計(jì)算明確精度范圍