BigDecimal使用不當,造成P0事故!
點擊關注下方公眾號,Java資料都在這里
來源:juejin.cn/post/7087404273503305736
背景
我們在使用金額計算或者展示金額的時候經常會使用BigDecimal,也是涉及金額時非常推薦的一個類型。
BigDecimal自身也提供了很多構造器方法,這些構造器方法使用不當可能會造成不必要的麻煩甚至是金額損失,從而引起事故資損。
接下來我們看下收銀臺出的一起事故。
【問題描述】
收銀臺計算商品金額報錯,導致訂單無法支付。
【事故級別】
P0
【過程】
13:44 接到報警,訂單支付失敗,支付可用率降至60%
13:50 迅速回滾上線代碼,恢復正常;
14:20 review代碼,預發(fā)布驗證發(fā)現(xiàn)問題點
14:58 修改問題代碼上線,線上恢復
【故障原因】 BigDecimal在金額計算中丟失精度
原因分析
首先我們先用一段代碼復現(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í)行結果如下:

通過測試發(fā)現(xiàn),當使用double或者float這些浮點數(shù)據(jù)類型時,會丟失精度,String、int則不會;這是為什么呢?
我們點開構造器方法看下源碼:
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對應)中提供了double與long轉換,doubleToRawLongBits就是將double轉換為long,這個方法是原始方法(底層不是java實現(xiàn),是c++實現(xiàn)的)。
double之所以會出問題,是因為小數(shù)點轉二進制丟失精度。BigDecimal在處理的時候把十進制小數(shù)擴大N倍讓它在整數(shù)上進行計算,并保留相應的精度信息

float和double類型,主要是為了科學計算和工程計算而設計的,之所以執(zhí)行二進制浮點運算,是為了在廣泛的數(shù)值范圍上提供較為精確的快速近和計算。
并沒有提供完全精確的結果,所以不應該被用于精確的結果的場合。
當浮點數(shù)達到一定大的數(shù),就會自動使用科學計數(shù)法,這樣的表示只是近似真實數(shù)而不等于真實數(shù)。
當十進制小數(shù)位轉換二進制的時候也會出現(xiàn)無限循環(huán)或者超過浮點數(shù)尾數(shù)的長度。
總結
所以,在涉及到精度計算的過程中,我們盡量使用String類型來進行轉換,正確用法如下:
BigDecimal bigDecimal2=new BigDecimal("8.8");BigDecimal bigDecimal3=new BigDecimal("8.812");System.out.println( bigDecimal2.compareTo(bigDecimal3));System.out.println( bigDecimal2.add(bigDecimal3));
BigDecimal創(chuàng)建出來的是對象,我們不能用傳統(tǒng)的加減乘除對其進行運算,必須使用他的方法,在我們數(shù)據(jù)庫存儲里,如果我們使用的是double或者float類型,需要進行來回的轉換后進行計算,非常不方便。
工具分享
所以在這里整理出一個util類供大家使用。
import?java.math.BigDecimal;/*** @Author shuaige* @Date 2022/4/17* @Version 1.0**/public class BigDecimalUtils {public static BigDecimal doubleAdd(double v1, double v2) {BigDecimal b1 = new BigDecimal(Double.toString(v1));BigDecimal b2 = new BigDecimal(Double.toString(v2));return b1.add(b2);}public static BigDecimal floatAdd(float v1, float v2) {BigDecimal b1 = new BigDecimal(Float.toString(v1));BigDecimal b2 = new BigDecimal(Float.toString(v2));return b1.add(b2);}public static BigDecimal doubleSub(double v1, double v2) {BigDecimal b1 = new BigDecimal(Double.toString(v1));BigDecimal b2 = new BigDecimal(Double.toString(v2));return b1.subtract(b2);}public static BigDecimal floatSub(float v1, float v2) {BigDecimal b1 = new BigDecimal(Float.toString(v1));BigDecimal b2 = new BigDecimal(Float.toString(v2));return b1.subtract(b2);}public static BigDecimal doubleMul(double v1, double v2) {BigDecimal b1 = new BigDecimal(Double.toString(v1));BigDecimal b2 = new BigDecimal(Double.toString(v2));return b1.multiply(b2);}public static BigDecimal floatMul(float v1, float v2) {BigDecimal b1 = new BigDecimal(Float.toString(v1));BigDecimal b2 = new BigDecimal(Float.toString(v2));return b1.multiply(b2);}public static BigDecimal doubleDiv(double v1, double v2) {BigDecimal b1 = new BigDecimal(Double.toString(v1));BigDecimal b2 = new BigDecimal(Double.toString(v2));// 保留小數(shù)點后兩位 ROUND_HALF_UP = 四舍五入return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);}public static BigDecimal floatDiv(float v1, float v2) {BigDecimal b1 = new BigDecimal(Float.toString(v1));BigDecimal b2 = new BigDecimal(Float.toString(v2));// 保留小數(shù)點后兩位 ROUND_HALF_UP = 四舍五入return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);}/*** 比較v1 v2大小* @param v1* @param v2* @return v1>v2 return 1 v1=v2 return 0 v1*/public static int doubleCompareTo(double v1, double v2) {BigDecimal b1 = new BigDecimal(Double.toString(v1));BigDecimal b2 = new BigDecimal(Double.toString(v2));return b1.compareTo(b2);}public static int floatCompareTo(float v1, float v2) {BigDecimal b1 = new BigDecimal(Float.toString(v1));BigDecimal b2 = new BigDecimal(Float.toString(v2));return b1.compareTo(b2);}}
? ? · END ·
熱門推薦:
PS:如果覺得我的分享不錯,歡迎大家隨手點贊、轉發(fā)、在看。

