<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    BigDecimal使用不當(dāng),造成P0事故!

    共 8267字,需瀏覽 17分鐘

     ·

    2022-07-05 12:08

    不點(diǎn)藍(lán)字關(guān)注,我們哪來故事?



    背景

    我們在使用金額計算或者展示金額的時候經(jīng)常會使用 BigDecimal,也是涉及金額時非常推薦的一個類型。

    BigDecimal 自身也提供了很多構(gòu)造器方法,這些構(gòu)造器方法使用不當(dāng)可能會造成不必要的麻煩甚至是金額損失,從而引起事故資損。

    基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能。

    項目地址:https://github.com/YunaiV/ruoyi-vue-pro

    事故

    接下來我們看下收銀臺出的一起事故。

    問題描述

    收銀臺計算商品金額報錯,導(dǎo)致訂單無法支付。

    事故級別

    P0

    事故過程

    如下:

    • 13:44,接到報警,訂單支付失敗,支付可用率降至 60%
    • 13:50,迅速回滾上線代碼,恢復(fù)正常
    • 14:20,review 代碼,預(yù)發(fā)布驗證發(fā)現(xiàn)問題點(diǎn)
    • 14:58,修改問題代碼上線,線上恢復(fù)

    故障原因

    BigDecimal 在金額計算中丟失精度。

    基于微服務(wù)的思想,構(gòu)建在 B2C 電商場景下的項目實戰(zhàn)。核心技術(shù)棧,是 Spring Boot + Dubbo 。未來,會重構(gòu)成 Spring Cloud Alibaba 。

    項目地址:https://github.com/YunaiV/onemall

    原因分析

    首先我們先用一段代碼復(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),當(dāng)使用 double 或者 float 這些浮點(diǎn)數(shù)據(jù)類型時,會丟失精度,String、int 則不會,這是為什么呢?

    我們點(diǎn)開構(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ù)點(diǎn)轉(zhuǎn)二進(jìn)制丟失精度。

    BigDecimal 在處理的時候把十進(jìn)制小數(shù)擴(kuò)大 N 倍讓它在整數(shù)上進(jìn)行計算,并保留相應(yīng)的精度信息。

    float 和 double 類型,主要是為了科學(xué)計算和工程計算而設(shè)計的,之所以執(zhí)行二進(jìn)制浮點(diǎn)運(yùn)算,是為了在廣泛的數(shù)值范圍上提供較為精確的快速近和計算。

    并沒有提供完全精確的結(jié)果,所以不應(yīng)該被用于精確的結(jié)果的場合。

    當(dāng)浮點(diǎn)數(shù)達(dá)到一定大的數(shù),就會自動使用科學(xué)計數(shù)法,這樣的表示只是近似真實數(shù)而不等于真實數(shù)。

    當(dāng)十進(jìn)制小數(shù)位轉(zhuǎn)換二進(jìn)制的時候也會出現(xiàn)無限循環(huán)或者超過浮點(diǎn)數(shù)尾數(shù)的長度。

    總結(jié)

    所以,在涉及到精度計算的過程中,我們盡量使用 String 類型來進(jìn)行轉(zhuǎn)換。

    正確用法如下:

    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)的加減乘除對其進(jìn)行運(yùn)算,必須使用他的方法,在我們數(shù)據(jù)庫存儲里,如果我們使用的是 double 或者 float 類型,需要進(jìn)行來回的轉(zhuǎn)換后進(jìn)行計算,非常不方便。

    工具分享

    所以在這里整理出一個 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ù)點(diǎn)后兩位 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ù)點(diǎn)后兩位 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<v2 return -1
         */

        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



    若覺得文章對你有幫助,隨手轉(zhuǎn)發(fā)分享,也是我們繼續(xù)更新的動力。


    長按二維碼,掃掃關(guān)注哦

    ?「C語言中文網(wǎng)」官方公眾號,關(guān)注手機(jī)閱讀教程 ?

    瀏覽 63
    點(diǎn)贊
    評論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報
    評論
    圖片
    表情
    推薦
    點(diǎn)贊
    評論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    黄色片日逼 | 国产亚洲视频在线 | 国产青青青 | 激情综合视频在线 | 欧美白人性爱 |