<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類巧妙處理Double類型精度丟失

          共 24872字,需瀏覽 50分鐘

           ·

          2021-09-30 23:24

          點(diǎn)擊上方“程序員大白”,選擇“星標(biāo)”公眾號

          重磅干貨,第一時(shí)間送達(dá)


          本篇要點(diǎn)

          • 簡單描述浮點(diǎn)數(shù)十進(jìn)制轉(zhuǎn)二進(jìn)制精度丟失的原因。

          • 介紹幾種創(chuàng)建BigDecimal方式的區(qū)別。

          • 整理了高精度計(jì)算的工具類。

          • 學(xué)習(xí)了阿里巴巴Java開發(fā)手冊關(guān)于BigDecimal比較相等的規(guī)定。

          經(jīng)典問題:浮點(diǎn)數(shù)精度丟失

          精度丟失的問題是在其他計(jì)算機(jī)語言中也都會出現(xiàn),float和double類型的數(shù)據(jù)在執(zhí)行二進(jìn)制浮點(diǎn)運(yùn)算的時(shí)候,并沒有提供完全精確的結(jié)果。產(chǎn)生誤差不在于數(shù)的大小,而是因?yàn)閿?shù)的精度。

          關(guān)于浮點(diǎn)數(shù)存儲精度丟失的問題,話題過于龐大,感興趣的同學(xué)可以自行搜索一下:【解惑】剖析float型的內(nèi)存存儲和精度丟失問題

          這里簡單討論一下十進(jìn)制數(shù)轉(zhuǎn)二進(jìn)制為什么會出現(xiàn)精度丟失的現(xiàn)象,十進(jìn)制數(shù)分為整數(shù)部分和小數(shù)部分,我們分開來看看就知道原因?yàn)楹危?/span>

          十進(jìn)制整數(shù)如何轉(zhuǎn)化為二進(jìn)制整數(shù)?

          將被除數(shù)每次都除以2,只要除到商為0就可以停止這個(gè)過程。

          5 / 2 = 2 余 1
          2 / 2 = 1 余 0
          1 / 2 = 0 余 1 
          // 結(jié)果為 101

          這個(gè)算法永遠(yuǎn)都不會無限循環(huán),整數(shù)永遠(yuǎn)都可以使用二進(jìn)制數(shù)精確表示,但小數(shù)呢?

          十進(jìn)制小數(shù)如何轉(zhuǎn)化為二進(jìn)制數(shù)?

          每次將小數(shù)部分乘2,取出整數(shù)部分,如果小數(shù)部分為0,就可以停止這個(gè)過程。

          0.1 * 2 = 0.2 取整數(shù)部分0
          0.2 * 2 = 0.4 取整數(shù)部分0
          0.4 * 2 = 0.8 取整數(shù)部分0
          0.8 * 2 = 1.6 取整數(shù)部分1
          0.6 * 2 = 1.2 取整數(shù)部分1
          0.2 * 2 = 0.4 取整數(shù)部分0 

          //... 我想寫到這就不必再寫了,你應(yīng)該也已經(jīng)發(fā)現(xiàn),
          // 上面的過程已經(jīng)開始循環(huán),小數(shù)部分永遠(yuǎn)不能為0

          這個(gè)算法有一定概率會存在無限循環(huán),即無法用有限長度的二進(jìn)制數(shù)表示十進(jìn)制的小數(shù),這就是精度丟失問題產(chǎn)生的原因。

          如何用BigDecimal解決double精度問題?

          我們已經(jīng)明白為什么精度會存在丟失現(xiàn)象,那么我們就應(yīng)該知道,當(dāng)某個(gè)業(yè)務(wù)場景對double數(shù)據(jù)的精度要求非常高時(shí),就必須采取某種手段來處理這個(gè)問題,這也是BigDecimal為什么會被廣泛應(yīng)用于金額支付場景中的原因啦。

          BigDecimal類位于java.math包下,用于對超過16位有效位的數(shù)進(jìn)行精確的運(yùn)算。一般來說,double類型的變量可以處理16位有效數(shù),但實(shí)際應(yīng)用中,如果超過16位,就需要BigDecimal類來操作。

          既然這樣,那用BigDecimal就能夠很好解決這個(gè)問題咯?

          public static void main(String[] args{
              // 方法1
                  BigDecimal a = new BigDecimal(0.1);
                  System.out.println("a --> " + a);
              // 方法2
                  BigDecimal b = new BigDecimal("0.1");
                  System.out.println("b --> " + b);
              // 方法3
                  BigDecimal c = BigDecimal.valueOf(0.1);
                  System.out.println("c --> " + c);
              }

          你可以思考一下,控制臺輸出會是啥。

          a --> 0.1000000000000000055511151231257827021181583404541015625
          b --> 0.1
          c --> 0.1

          可以看到,使用方法一的構(gòu)造函數(shù)仍然出現(xiàn)了精度丟失的問題,而方法二和方法三符合我們的預(yù)期,為什么會這樣呢?

          這三個(gè)方法其實(shí)對應(yīng)著三種不同的構(gòu)造函數(shù):

              // 傳入double
              public BigDecimal(double val) {
                  this(val,MathContext.UNLIMITED);
              }
              // 傳入string
              public BigDecimal(String val) {
                  this(val.toCharArray(), 0val.length());
              }

              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.
                  // 可以看到實(shí)際上就是第二種
                  return new BigDecimal(Double.toString(val));
              }

          關(guān)于這三個(gè)構(gòu)造函數(shù),JDK已經(jīng)給出了解釋,并用Notes標(biāo)注:

          為了防止以后圖片可能會存在顯示問題,這里再記錄一下:

          new BigDecimal(double val)

          該方法是不可預(yù)測的,以0.1為例,你以為你傳了一個(gè)double類型的0.1,最后會返回一個(gè)值為0.1的BigDecimal嗎?不會的,原因在于,0.1無法用有限長度的二進(jìn)制數(shù)表示,無法精確地表示為雙精度數(shù),最后的結(jié)果會是0.100000xxx。

          new BigDecimal(String val)

          該方法是完全可預(yù)測的,也就是說你傳入一個(gè)字符串"0.1",他就會給你返回一個(gè)值完全為0,1的BigDecimal,官方也表示,能用這個(gè)構(gòu)造函數(shù)就用這個(gè)構(gòu)造函數(shù)叭。

          BigDecimal.valueOf(double val)

          第二種構(gòu)造方式已經(jīng)足夠優(yōu)秀,可你還是想傳入一個(gè)double值,怎么辦呢?官方其實(shí)提供給你思路并且實(shí)現(xiàn)了它,可以使用Double.toString(double val)先將double值轉(zhuǎn)為String,再調(diào)用第二種構(gòu)造方式,你可以直接使用靜態(tài)方法:valueOf(double val)

          Double的加減乘除運(yùn)算工具類

          BigDecimal所創(chuàng)建的是對象,故我們不能使用傳統(tǒng)的+、-、*、/等算術(shù)運(yùn)算符直接對其對象進(jìn)行數(shù)學(xué)運(yùn)算,而必須調(diào)用其相對應(yīng)的方法。方法中的參數(shù)也必須是BigDecimal的對象。網(wǎng)上有很多這樣的工具類,這邊直接貼一下,邏輯不難,主要為了簡化項(xiàng)目中頻繁互相轉(zhuǎn)化的問題。

          /**
           * 用于高精確處理常用的數(shù)學(xué)運(yùn)算
           */

          public class ArithmeticUtils {
              //默認(rèn)除法運(yùn)算精度
              private static final int DEF_DIV_SCALE = 10;

              /**
               * 提供精確的加法運(yùn)算
               *
               * @param v1 被加數(shù)
               * @param v2 加數(shù)
               * @return 兩個(gè)參數(shù)的和
               */


              public static double add(double v1, double v2) {
                  BigDecimal b1 = new BigDecimal(Double.toString(v1));
                  BigDecimal b2 = new BigDecimal(Double.toString(v2));
                  return b1.add(b2).doubleValue();
              }

              /**
               * 提供精確的加法運(yùn)算
               *
               * @param v1 被加數(shù)
               * @param v2 加數(shù)
               * @return 兩個(gè)參數(shù)的和
               */

              public static BigDecimal add(String v1, String v2) {
                  BigDecimal b1 = new BigDecimal(v1);
                  BigDecimal b2 = new BigDecimal(v2);
                  return b1.add(b2);
              }

              /**
               * 提供精確的加法運(yùn)算
               *
               * @param v1    被加數(shù)
               * @param v2    加數(shù)
               * @param scale 保留scale 位小數(shù)
               * @return 兩個(gè)參數(shù)的和
               */

              public static String add(String v1, String v2, int scale) {
                  if (scale < 0) {
                      throw new IllegalArgumentException(
                              "The scale must be a positive integer or zero");
                  }
                  BigDecimal b1 = new BigDecimal(v1);
                  BigDecimal b2 = new BigDecimal(v2);
                  return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
              }

              /**
               * 提供精確的減法運(yùn)算
               *
               * @param v1 被減數(shù)
               * @param v2 減數(shù)
               * @return 兩個(gè)參數(shù)的差
               */

              public static double sub(double v1, double v2) {
                  BigDecimal b1 = new BigDecimal(Double.toString(v1));
                  BigDecimal b2 = new BigDecimal(Double.toString(v2));
                  return b1.subtract(b2).doubleValue();
              }

              /**
               * 提供精確的減法運(yùn)算。
               *
               * @param v1 被減數(shù)
               * @param v2 減數(shù)
               * @return 兩個(gè)參數(shù)的差
               */

              public static BigDecimal sub(String v1, String v2) {
                  BigDecimal b1 = new BigDecimal(v1);
                  BigDecimal b2 = new BigDecimal(v2);
                  return b1.subtract(b2);
              }

              /**
               * 提供精確的減法運(yùn)算
               *
               * @param v1    被減數(shù)
               * @param v2    減數(shù)
               * @param scale 保留scale 位小數(shù)
               * @return 兩個(gè)參數(shù)的差
               */

              public static String sub(String v1, String v2, int scale) {
                  if (scale < 0) {
                      throw new IllegalArgumentException(
                              "The scale must be a positive integer or zero");
                  }
                  BigDecimal b1 = new BigDecimal(v1);
                  BigDecimal b2 = new BigDecimal(v2);
                  return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
              }

              /**
               * 提供精確的乘法運(yùn)算
               *
               * @param v1 被乘數(shù)
               * @param v2 乘數(shù)
               * @return 兩個(gè)參數(shù)的積
               */

              public static double mul(double v1, double v2) {
                  BigDecimal b1 = new BigDecimal(Double.toString(v1));
                  BigDecimal b2 = new BigDecimal(Double.toString(v2));
                  return b1.multiply(b2).doubleValue();
              }

              /**
               * 提供精確的乘法運(yùn)算
               *
               * @param v1 被乘數(shù)
               * @param v2 乘數(shù)
               * @return 兩個(gè)參數(shù)的積
               */

              public static BigDecimal mul(String v1, String v2) {
                  BigDecimal b1 = new BigDecimal(v1);
                  BigDecimal b2 = new BigDecimal(v2);
                  return b1.multiply(b2);
              }

              /**
               * 提供精確的乘法運(yùn)算
               *
               * @param v1    被乘數(shù)
               * @param v2    乘數(shù)
               * @param scale 保留scale 位小數(shù)
               * @return 兩個(gè)參數(shù)的積
               */

              public static double mul(double v1, double v2, int scale) {
                  BigDecimal b1 = new BigDecimal(Double.toString(v1));
                  BigDecimal b2 = new BigDecimal(Double.toString(v2));
                  return round(b1.multiply(b2).doubleValue(), scale);
              }

              /**
               * 提供精確的乘法運(yùn)算
               *
               * @param v1    被乘數(shù)
               * @param v2    乘數(shù)
               * @param scale 保留scale 位小數(shù)
               * @return 兩個(gè)參數(shù)的積
               */

              public static String mul(String v1, String v2, int scale) {
                  if (scale < 0) {
                      throw new IllegalArgumentException(
                              "The scale must be a positive integer or zero");
                  }
                  BigDecimal b1 = new BigDecimal(v1);
                  BigDecimal b2 = new BigDecimal(v2);
                  return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
              }

              /**
               * 提供(相對)精確的除法運(yùn)算,當(dāng)發(fā)生除不盡的情況時(shí),精確到
               * 小數(shù)點(diǎn)以后10位,以后的數(shù)字四舍五入
               *
               * @param v1 被除數(shù)
               * @param v2 除數(shù)
               * @return 兩個(gè)參數(shù)的商
               */


              public static double div(double v1, double v2) {
                  return div(v1, v2, DEF_DIV_SCALE);
              }

              /**
               * 提供(相對)精確的除法運(yùn)算。當(dāng)發(fā)生除不盡的情況時(shí),由scale參數(shù)指
               * 定精度,以后的數(shù)字四舍五入
               *
               * @param v1    被除數(shù)
               * @param v2    除數(shù)
               * @param scale 表示表示需要精確到小數(shù)點(diǎn)以后幾位。
               * @return 兩個(gè)參數(shù)的商
               */

              public static double div(double v1, double v2, int scale) {
                  if (scale < 0) {
                      throw new IllegalArgumentException("The scale must be a positive integer or zero");
                  }
                  BigDecimal b1 = new BigDecimal(Double.toString(v1));
                  BigDecimal b2 = new BigDecimal(Double.toString(v2));
                  return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
              }

              /**
               * 提供(相對)精確的除法運(yùn)算。當(dāng)發(fā)生除不盡的情況時(shí),由scale參數(shù)指
               * 定精度,以后的數(shù)字四舍五入
               *
               * @param v1    被除數(shù)
               * @param v2    除數(shù)
               * @param scale 表示需要精確到小數(shù)點(diǎn)以后幾位
               * @return 兩個(gè)參數(shù)的商
               */

              public static String div(String v1, String v2, int scale) {
                  if (scale < 0) {
                      throw new IllegalArgumentException("The scale must be a positive integer or zero");
                  }
                  BigDecimal b1 = new BigDecimal(v1);
                  BigDecimal b2 = new BigDecimal(v1);
                  return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
              }

              /**
               * 提供精確的小數(shù)位四舍五入處理
               *
               * @param v     需要四舍五入的數(shù)字
               * @param scale 小數(shù)點(diǎn)后保留幾位
               * @return 四舍五入后的結(jié)果
               */

              public static double round(double v, int scale) {
                  if (scale < 0) {
                      throw new IllegalArgumentException("The scale must be a positive integer or zero");
                  }
                  BigDecimal b = new BigDecimal(Double.toString(v));
                  return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
              }

              /**
               * 提供精確的小數(shù)位四舍五入處理
               *
               * @param v     需要四舍五入的數(shù)字
               * @param scale 小數(shù)點(diǎn)后保留幾位
               * @return 四舍五入后的結(jié)果
               */

              public static String round(String v, int scale) {
                  if (scale < 0) {
                      throw new IllegalArgumentException(
                              "The scale must be a positive integer or zero");
                  }
                  BigDecimal b = new BigDecimal(v);
                  return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
              }

              /**
               * 取余數(shù)
               *
               * @param v1    被除數(shù)
               * @param v2    除數(shù)
               * @param scale 小數(shù)點(diǎn)后保留幾位
               * @return 余數(shù)
               */

              public static String remainder(String v1, String v2, int scale) {
                  if (scale < 0) {
                      throw new IllegalArgumentException(
                              "The scale must be a positive integer or zero");
                  }
                  BigDecimal b1 = new BigDecimal(v1);
                  BigDecimal b2 = new BigDecimal(v2);
                  return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
              }

              /**
               * 取余數(shù)  BigDecimal
               *
               * @param v1    被除數(shù)
               * @param v2    除數(shù)
               * @param scale 小數(shù)點(diǎn)后保留幾位
               * @return 余數(shù)
               */

              public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
                  if (scale < 0) {
                      throw new IllegalArgumentException(
                              "The scale must be a positive integer or zero");
                  }
                  return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
              }

              /**
               * 比較大小
               * 阿里巴巴開發(fā)規(guī)范明確:比較BigDecimal的等值需要使用compareTo,不可用equals
               * equals會比較值和精度,compareTo會忽略精度
               * @param v1 被比較數(shù)
               * @param v2 比較數(shù)
               * @return 如果v1 大于v2 則 返回true 否則false
               */

              public static boolean compare(String v1, String v2) {
                  BigDecimal b1 = new BigDecimal(v1);
                  BigDecimal b2 = new BigDecimal(v2);
                  int bj = b1.compareTo(b2);
                  boolean res;
                  if (bj > 0)
                      res = true;
                  else
                      res = false;
                  return res;
              }
          }

          阿里巴巴Java開發(fā)手冊關(guān)于BigDecimal的規(guī)定

          【強(qiáng)制】如上所示BigDecimal的等值比較應(yīng)使用compareTo()方法,而不是equals()方法。

          說明:equals()方法會比較值和精度(1.0和1.00返回結(jié)果為false),而compareTo()則會忽略精度。

          關(guān)于這一點(diǎn),我們來看一個(gè)例子就明白了:

          public static void main(String[] args{
                  BigDecimal a = new BigDecimal("1");
                  BigDecimal b = new BigDecimal("1.0");
                  System.out.println(a.equals(b)); // false
                  System.out.println(a.compareTo(b)); //0 表示相等
              }

          JDK中對這兩個(gè)方法的解釋是這樣的:

          • 使用compareTo方法,兩個(gè)值相等但是精度不同的BigDecimal對象會被認(rèn)為是相等的,比如2.0和2.00。建議使用x.compareTo(y) <op> 0來表示(<, == , > , >= , != , <=)中的其中一個(gè)關(guān)系,<op>就表示運(yùn)算符。

          • equals方法與compareTo方法不同,此方法僅在兩個(gè)BigDecimal對象的值和精度都相等時(shí)才被認(rèn)為是相等的,如2.0和2.00就是不相等的。

          參考閱讀

          • LanceToBigData:Java之BigDecimal詳解

          • 為什么阿里巴巴禁止使用BigDecimal的equals方法做等值比較?

          • java中double和float精度丟失問題

          • 討Java中double在計(jì)算時(shí)精度丟失的問題

          source: https://www.cnblogs.com/summerday152/p/14202267.html

          國產(chǎn)小眾瀏覽器因屏蔽視頻廣告,被索賠100萬(后續(xù))

          年輕人“不講武德”:因看黃片上癮,把網(wǎng)站和786名女主播起訴了

          中國聯(lián)通官網(wǎng)被發(fā)現(xiàn)含木馬腳本,可向用戶推廣色情APP

          張一鳴:每個(gè)逆襲的年輕人,都具備的底層能力


          關(guān)


          學(xué)西學(xué)學(xué)運(yùn)護(hù)質(zhì)結(jié)關(guān)[]學(xué)習(xí)進(jìn)


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

          手機(jī)掃一掃分享

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

          手機(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>
                  开心激情网站 | 亚洲色图第十页 | 奇米777欧美成人在线 | 久久久久久久久国产精品视频 | 亚洲无套 |