BigDecimal使用不當,造成P0事故!
背景
我們在使用金額計算或者展示金額的時候經(jīng)常會使用 BigDecimal,也是涉及金額時非常推薦的一個類型。
BigDecimal 自身也提供了很多構(gòu)造器方法,這些構(gòu)造器方法使用不當可能會造成不必要的麻煩甚至是金額損失,從而引起事故資損。
事故
接下來我們看下收銀臺出的一起事故。
問題描述
收銀臺計算商品金額報錯,導(dǎo)致訂單無法支付。
事故級別
P0
事故過程
如下:
13:44,接到報警,訂單支付失敗,支付可用率降至 60% 13:50,迅速回滾上線代碼,恢復(fù)正常 14:20,review 代碼,預(yù)發(fā)布驗證發(fā)現(xiàn)問題點 14:58,修改問題代碼上線,線上恢復(fù)
故障原因
BigDecimal 在金額計算中丟失精度。
原因分析
首先我們先用一段代碼復(fù)現(xiàn)問題根源,如下所示:
public static void main(String[] args) {
BigDecimal bigDecimal=new BigDecimal(88);
System.out.println(bigDecimal);
bigDecimal=new BigDecimal("8.8");
System.out.println(bigDecimal);
bigDecimal=new BigDecimal(8.8);
System.out.println(bigDecimal);
}
執(zhí)行結(jié)果如下:

通過測試發(fā)現(xiàn),當使用 double 或者 float 這些浮點數(shù)據(jù)類型時,會丟失精度,String、int 則不會,這是為什么呢?
我們點開構(gòu)造器方法看下源碼:
public static long doubleToLongBits(double value) {
long result = doubleToRawLongBits(value);
// Check for NaN based on values of bit fields, maximum
// exponent and nonzero significand.
if ( ((result & DoubleConsts.EXP_BIT_MASK) ==
DoubleConsts.EXP_BIT_MASK) &&
(result & DoubleConsts.SIGNIF_BIT_MASK) != 0L)
result = 0x7ff8000000000000L;
return result;
}
問題就處在 doubleToRawLongBits 這個方法上,在 jdk 中 double 類(float 與 int 對應(yīng))中提供了 double 與 long 轉(zhuǎn)換,doubleToRawLongBits 就是將 double 轉(zhuǎn)換為 long,這個方法是原始方法(底層不是 java 實現(xiàn),是 c++ 實現(xiàn)的)。
double 之所以會出問題,是因為小數(shù)點轉(zhuǎn)二進制丟失精度。

BigDecimal 在處理的時候把十進制小數(shù)擴大 N 倍讓它在整數(shù)上進行計算,并保留相應(yīng)的精度信息。
① float 和 double 類型,主要是為了科學(xué)計算和工程計算而設(shè)計的,之所以執(zhí)行二進制浮點運算,是為了在廣泛的數(shù)值范圍上提供較為精確的快速近和計算。
② 并沒有提供完全精確的結(jié)果,所以不應(yīng)該被用于精確的結(jié)果的場合。
③ 當浮點數(shù)達到一定大的數(shù),就會自動使用科學(xué)計數(shù)法,這樣的表示只是近似真實數(shù)而不等于真實數(shù)。
④ 當十進制小數(shù)位轉(zhuǎn)換二進制的時候也會出現(xiàn)無限循環(huán)或者超過浮點數(shù)尾數(shù)的長度。
總結(jié)
所以,在涉及到精度計算的過程中,我們盡量使用 String 類型來進行轉(zhuǎn)換。
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

面試題】即可獲取