<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          用了BigDecimal就不會(huì)資損?了解下BigDecimal這五個(gè)坑

          共 5236字,需瀏覽 11分鐘

           ·

          2022-08-02 01:32

          往期熱門文章:

          1、一個(gè)依賴搞定 Spring Boot 反爬蟲,防止接口盜刷!

          2、千萬不要把 Request 傳遞到異步線程里面!有坑!

          3、不卷了!入職字節(jié)一周就果斷跑了。

          4、SpringBoot+ShardingSphereJDBC實(shí)現(xiàn)讀寫分離!

          5、不好意思, Maven 該換了!

          上周看到一篇因?yàn)樵诮痤~計(jì)算中沒有使用BigDecimal而導(dǎo)致故障的文章,但是除非在一些非常簡單的場(chǎng)景,結(jié)算匯金類的業(yè)務(wù)也不會(huì)直接用BigDecimal來計(jì)算金額,原因有兩點(diǎn):
          1. BigDecimal里面還是有很多隱蔽的坑的
          2. BigDecimal沒有提供金額的單位

          1. BigDecimal中的五個(gè)容易踩的坑

          1.1 new BigDecimal()還是BigDecimal#valueOf()

          先看下面這段代碼
          BigDecimal bd1 = new BigDecimal(0.01);
          BigDecimal bd2 = BigDecimal.valueOf(0.01);
          System.out.println("bd1 = " + bd1);
          System.out.println("bd2 = " + bd2);
          輸出到控制臺(tái)的結(jié)果是:
          bd1 = 0.01000000000000000020816681711721685132943093776702880859375
          bd2 = 0.01
          造成這種差異的原因是0.1這個(gè)數(shù)字計(jì)算機(jī)是無法精確表示的,送給BigDecimal的時(shí)候就已經(jīng)丟精度了,而BigDecimal#valueOf的實(shí)現(xiàn)卻完全不同
          public static BigDecimal valueOf(double val) {
              // Reminder: a zero double returns '0.0', so we cannot fastpath
              // to use the constant ZERO.  This might be important enough to
              // justify a factory approach, a cache, or a few private
              // constants, later.
              return new BigDecimal(Double.toString(val));
          }
          它使用了浮點(diǎn)數(shù)相應(yīng)的字符串來構(gòu)造BigDecimal對(duì)象,因此避免了精度問題。所以大家要盡量要使用字符串而不是浮點(diǎn)數(shù)去構(gòu)造BigDecimal對(duì)象,如果實(shí)在不行,就使用BigDecimal#valueOf()方法吧。

          1.2 等值比較

          BigDecimal bd1 = new BigDecimal("1.0");
          BigDecimal bd2 = new BigDecimal("1.00");
          System.out.println(bd1.equals(bd2));
          System.out.println(bd1.compareTo(bd2));
          控制臺(tái)的輸出將會(huì)是:
          false
          0
          究其原因是,BigDecimalequals方法的實(shí)現(xiàn)會(huì)比較兩個(gè)數(shù)字的精度,而compareTo方法則只會(huì)比較數(shù)值的大小。

          1.3 BigDecimal并不代表無限精度

          先看這段代碼
          BigDecimal a = new BigDecimal("1.0");
          BigDecimal b = new BigDecimal("3.0");
          a.divide(b) // results in the following exception.
          結(jié)果會(huì)拋出異常:
          java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
          關(guān)于這個(gè)異常,Oracle的官方文檔有具體說明
          If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations.
          大意是,如果除法的商的結(jié)果是一個(gè)無限小數(shù)但是我們期望返回精確的結(jié)果,那程序就會(huì)拋出異常。回到我們的這個(gè)例子,我們需要告訴JVM我們不需要返回精確的結(jié)果就好了
          BigDecimal a = new BigDecimal("1.0");
          BigDecimal b = new BigDecimal("3.0");
          a.divide(b, 2, RoundingMode.HALF_UP)// 0.33

          1.4 BigDecimal轉(zhuǎn)回String要小心

          BigDecimal d = BigDecimal.valueOf(12334535345456700.12345634534534578901);
          String out = d.toString(); // Or perform any formatting that needs to be done
          System.out.println(out); // 1.23345353454567E+16
          可以看到結(jié)果已經(jīng)被轉(zhuǎn)換成了科學(xué)計(jì)數(shù)法,可能這個(gè)并不是預(yù)期的結(jié)果BigDecimal有三個(gè)方法可以轉(zhuǎn)為相應(yīng)的字符串類型,切記不要用錯(cuò):
          String toString();     // 有必要時(shí)使用科學(xué)計(jì)數(shù)法
          String toPlainString();   // 不使用科學(xué)計(jì)數(shù)法
          String toEngineeringString();  // 工程計(jì)算中經(jīng)常使用的記錄數(shù)字的方法,與科學(xué)計(jì)數(shù)法類似,但要求10的冪必須是3的倍數(shù)

          1.5 執(zhí)行順序不能調(diào)換(乘法交換律失效)

          乘法滿足交換律是一個(gè)常識(shí),但是在計(jì)算機(jī)的世界里,會(huì)出現(xiàn)不滿足乘法交換律的情況
          BigDecimal a = BigDecimal.valueOf(1.0);
          BigDecimal b = BigDecimal.valueOf(3.0);
          BigDecimal c = BigDecimal.valueOf(3.0);
          System.out.println(a.divide(b, 2, RoundingMode.HALF_UP).multiply(c)); // 0.990
          System.out.println(a.multiply(c).divide(b, 2, RoundingMode.HALF_UP)); // 1.00
          別小看這這0.01的差別,在匯金領(lǐng)域,會(huì)產(chǎn)生非常大的金額差異。

          2. 最佳實(shí)踐

          關(guān)于金額計(jì)算,很多業(yè)務(wù)團(tuán)隊(duì)會(huì)基于BigDecimal再封裝一個(gè)Money類,其實(shí)我們直接可以用一個(gè)半官方的Money類:JSR 354 ,雖然沒能在Java 9中成為Java標(biāo)準(zhǔn),很有可能集成到后續(xù)的Java版本中成為官方庫。

          2.1 maven坐標(biāo)

          <dependency>
              <groupId>org.javamoney</groupId>
              <artifactId>moneta</artifactId>
              <version>1.1</version>
          </dependency>

          2.2 新建Money

          CurrencyUnit cny = Monetary.getCurrency("CNY");
          Money money = Money.of(1.0, cny); 
          // 或者 Money money = Money.of(1.0, "CNY");
          //System.out.println(money);

          2.3 金額運(yùn)算

          CurrencyUnit cny = Monetary.getCurrency("CNY");
          Money oneYuan = Money.of(1.0, cny);
          Money threeYuan = oneYuan.add(Money.of(2.0"CNY")); //CNY 3
          Money tenYuan = oneYuan.multiply(10); // CNY 10
          Money fiveFen = oneYuan.divide(2); //CNY 0.5

          2.4 比較相等

          Money fiveFen = Money.of(0.5"CNY"); //CNY 0.5
          Money anotherFiveFen = Money.of(0.50"CNY"); // CNY 0.50
          System.out.println(fiveFen.equals(anotherFiveFen)); // true
          可以看到,這個(gè)類對(duì)金額做了顯性的抽象,增加了金額的單位,也避免了直接使用BigDecimal的一些坑。
          往期熱門文章:

          1、線上MySQL的自增id用盡怎么辦?被面試官干趴下了!
          2、計(jì)算機(jī)專業(yè)會(huì)不會(huì)成為下一個(gè)土木?
          3、xxl-job驚艷的設(shè)計(jì),怎能叫人不愛
          4、ArrayList#subList這四個(gè)坑,一不小心就中招
          5、面試官:大量請(qǐng)求 Redis 不存在的數(shù)據(jù),從而影響數(shù)據(jù)庫,該如何解決?
          6、MySQL 暴跌!
          7、超越 Xshell!號(hào)稱下一代 Terminal 終端神器,用完愛不釋手!
          8、IDEA 官宣全新默認(rèn) UI,太震撼了!!
          9、讓你直呼「臥槽」的 GitHub 項(xiàng)目!
          10、Kafka又笨又重,為啥不選Redis?

          ?

          瀏覽 34
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩A∨视频 | 国产精品揄拍100视频 | 精品视频在线观看免费 | 2016超碰| 嫩草黄片 |