背景
我們在使用金額計算或者展示金額的時候經常會使用 BigDecimal,也是涉及金額時非常推薦的一個類型。
BigDecimal 自身也提供了很多構造器方法,這些構造器方法使用不當可能會造成不必要的麻煩甚至是金額損失,從而引起事故資損。
事故
接下來我們看下收銀臺出的一起事故。
問題描述
收銀臺計算商品金額報錯,導致訂單無法支付。
事故級別
P0
事故過程
如下:
13:44,接到報警,訂單支付失敗,支付可用率降至 60%
13:50,迅速回滾上線代碼,恢復正常
14:20,review 代碼,預發布驗證發現問題點
14:58,修改問題代碼上線,線上恢復
故障原因
BigDecimal 在金額計算中丟失精度。
原因分析
首先我們先用一段代碼復現問題根源,如下所示:
publicstaticvoidmain(String[]args){ BigDecimalbigDecimal=newBigDecimal(88); System.out.println(bigDecimal); bigDecimal=newBigDecimal("8.8"); System.out.println(bigDecimal); bigDecimal=newBigDecimal(8.8); System.out.println(bigDecimal); }
執行結果如下:
通過測試發現,當使用 double 或者 float 這些浮點數據類型時,會丟失精度,String、int 則不會,這是為什么呢?
我們點開構造器方法看下源碼:
publicstaticlongdoubleToLongBits(doublevalue){ longresult=doubleToRawLongBits(value); //CheckforNaNbasedonvaluesofbitfields,maximum //exponentandnonzerosignificand. if(((result&DoubleConsts.EXP_BIT_MASK)== DoubleConsts.EXP_BIT_MASK)&& (result&DoubleConsts.SIGNIF_BIT_MASK)!=0L) result=0x7ff8000000000000L; returnresult; }
問題就處在 doubleToRawLongBits 這個方法上,在 jdk 中 double 類(float 與 int 對應)中提供了 double 與 long 轉換,doubleToRawLongBits 就是將 double 轉換為 long,這個方法是原始方法(底層不是 java 實現,是 c++ 實現的)。
double 之所以會出問題,是因為小數點轉二進制丟失精度。
BigDecimal 在處理的時候把十進制小數擴大 N 倍讓它在整數上進行計算,并保留相應的精度信息。
① float 和 double 類型,主要是為了科學計算和工程計算而設計的,之所以執行二進制浮點運算,是為了在廣泛的數值范圍上提供較為精確的快速近和計算。
② 并沒有提供完全精確的結果,所以不應該被用于精確的結果的場合。
③ 當浮點數達到一定大的數,就會自動使用科學計數法,這樣的表示只是近似真實數而不等于真實數。
④ 當十進制小數位轉換二進制的時候也會出現無限循環或者超過浮點數尾數的長度。
總結
所以,在涉及到精度計算的過程中,我們盡量使用 String 類型來進行轉換。
編輯:黃飛
-
浮點數
+關注
關注
0文章
60瀏覽量
15864 -
代碼
+關注
關注
30文章
4752瀏覽量
68360 -
string
+關注
關注
0文章
40瀏覽量
4719
原文標題:BigDecimal使用不當,造成P0事故!
文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論