<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類型精度丟失

          共 11102字,需瀏覽 23分鐘

           ·

          2020-12-30 05:15

          51f2f83858396f27494787a98a3b9b8f.webp

          點擊上方「藍字」關注我們

          9692dc7fd19f730a744eebe57115f004.webp

          本篇要點

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

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

          • 整理了高精度計算的工具類。

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

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

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

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

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

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

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

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

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

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

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

          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?

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

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

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

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

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

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

          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)了精度丟失的問題,而方法二和方法三符合我們的預期,為什么會這樣呢?

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

          ????//?傳入double
          ????public?BigDecimal(double?val)?{
          ????????this(val,MathContext.UNLIMITED);
          ????}
          ????//?傳入string
          ????public?BigDecimal(String?val)?{
          ????????this(val.toCharArray(),?0,?val.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.
          ????????//?可以看到實際上就是第二種
          ????????return?new?BigDecimal(Double.toString(val));
          ????}

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

          84ce4c092dcc1874701920584a1b6484.webp

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

          new BigDecimal(double val)

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

          new BigDecimal(String val)

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

          BigDecimal.valueOf(double val)

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

          Double的加減乘除運算工具類

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

          /**
          ?*?用于高精確處理常用的數(shù)學運算
          ?*/

          public?class?ArithmeticUtils?{
          ????//默認除法運算精度
          ????private?static?final?int?DEF_DIV_SCALE?=?10;

          ????/**
          ?????*?提供精確的加法運算
          ?????*
          ?????*?@param?v1?被加數(shù)
          ?????*?@param?v2?加數(shù)
          ?????*?@return?兩個參數(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();
          ????}

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

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

          ????/**
          ?????*?提供精確的加法運算
          ?????*
          ?????*?@param?v1????被加數(shù)
          ?????*?@param?v2????加數(shù)
          ?????*?@param?scale?保留scale?位小數(shù)
          ?????*?@return?兩個參數(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();
          ????}

          ????/**
          ?????*?提供精確的減法運算
          ?????*
          ?????*?@param?v1?被減數(shù)
          ?????*?@param?v2?減數(shù)
          ?????*?@return?兩個參數(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();
          ????}

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

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

          ????/**
          ?????*?提供精確的減法運算
          ?????*
          ?????*?@param?v1????被減數(shù)
          ?????*?@param?v2????減數(shù)
          ?????*?@param?scale?保留scale?位小數(shù)
          ?????*?@return?兩個參數(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();
          ????}

          ????/**
          ?????*?提供精確的乘法運算
          ?????*
          ?????*?@param?v1?被乘數(shù)
          ?????*?@param?v2?乘數(shù)
          ?????*?@return?兩個參數(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();
          ????}

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

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

          ????/**
          ?????*?提供精確的乘法運算
          ?????*
          ?????*?@param?v1????被乘數(shù)
          ?????*?@param?v2????乘數(shù)
          ?????*?@param?scale?保留scale?位小數(shù)
          ?????*?@return?兩個參數(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);
          ????}

          ????/**
          ?????*?提供精確的乘法運算
          ?????*
          ?????*?@param?v1????被乘數(shù)
          ?????*?@param?v2????乘數(shù)
          ?????*?@param?scale?保留scale?位小數(shù)
          ?????*?@return?兩個參數(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();
          ????}

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


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

          ????/**
          ?????*?提供(相對)精確的除法運算。當發(fā)生除不盡的情況時,由scale參數(shù)指
          ?????*?定精度,以后的數(shù)字四舍五入
          ?????*
          ?????*?@param?v1????被除數(shù)
          ?????*?@param?v2????除數(shù)
          ?????*?@param?scale 表示表示需要精確到小數(shù)點以后幾位。
          ?????*?@return?兩個參數(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();
          ????}

          ????/**
          ?????*?提供(相對)精確的除法運算。當發(fā)生除不盡的情況時,由scale參數(shù)指
          ?????*?定精度,以后的數(shù)字四舍五入
          ?????*
          ?????*?@param?v1????被除數(shù)
          ?????*?@param?v2????除數(shù)
          ?????*?@param?scale?表示需要精確到小數(shù)點以后幾位
          ?????*?@return?兩個參數(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ù)點后保留幾位
          ?????*?@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ù)點后保留幾位
          ?????*?@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ù)點后保留幾位
          ?????*?@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ù)點后保留幾位
          ?????*?@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ā)手冊關于BigDecimal的規(guī)定

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

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

          關于這一點,我們來看一個例子就明白了:

          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中對這兩個方法的解釋是這樣的:

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

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

          參考閱讀

          • LanceToBigData:Java之BigDecimal詳解

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

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

          • 討Java中double在計算時精度丟失的問題

          source:?https://www.cnblogs.com/summerday152/p/14202267.html
          58f4ac3fddd8a428fc1469235c3abf30.webp

          掃碼二維碼

          獲取更多精彩

          Java樂園

          45addf6f03ca9637fcaa7612499cae3a.webp有用!分享+在看?
          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  可以免费观看的黄色视屏 | 亚洲资源在线 | 亚洲17p | 手机免费看AV片 | 午夜日逼网 |