<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后,計(jì)算結(jié)果就一定精確了?

          共 4355字,需瀏覽 9分鐘

           ·

          2021-02-06 10:51

          BigDecimal,相信對(duì)于很多人來(lái)說(shuō)都不陌生,很多人都知道他的用法,這是一種java.math包中提供的一種可以用來(lái)進(jìn)行精確運(yùn)算的類(lèi)型。

          很多人都知道,在進(jìn)行金額表示、金額計(jì)算等場(chǎng)景,不能使用double、float等類(lèi)型,而是要使用對(duì)精度支持的更好的BigDecimal。

          所以,很多支付、電商、金融等業(yè)務(wù)中,BigDecimal的使用非常頻繁。但是,如果誤以為只要使用BigDecimal表示數(shù)字,結(jié)果就一定精確,那就大錯(cuò)特錯(cuò)了!

          在之前的一篇文章中,我們介紹過(guò),使用BigDecimal的equals方法并不能驗(yàn)證兩個(gè)數(shù)是否真的相等(為什么阿里巴巴禁止使用BigDecimal的equals方法做等值比較?)。

          除了這個(gè)情況,BigDecimal的使用的第一步就是創(chuàng)建一個(gè)BigDecimal對(duì)象,如果這一步都有問(wèn)題,那么后面怎么算都是錯(cuò)的!

          那到底應(yīng)該如何正確的創(chuàng)建一個(gè)BigDecimal?

          關(guān)于這個(gè)問(wèn)題,我Review過(guò)很多代碼,也面試過(guò)很多一線開(kāi)發(fā),很多人都掉進(jìn)坑里過(guò)。這是一個(gè)很容易被忽略,但是又影響重大的問(wèn)題。

          關(guān)于這個(gè)問(wèn)題,在《阿里巴巴Java開(kāi)發(fā)手冊(cè)》中有一條建議,或者說(shuō)是要求:

          ?

          這是一條【強(qiáng)制】建議,那么,這背后的原理是什么呢?

          想要搞清楚這個(gè)問(wèn)題,主要需要弄清楚以下幾個(gè)問(wèn)題:

          1、為什么說(shuō)double不精確?

          2、BigDecimal是如何保證精確的?

          在知道這兩個(gè)問(wèn)題的答案之后,我們也就大概知道為什么不能使用BigDecimal(double)來(lái)創(chuàng)建一個(gè)BigDecimal了。




          double為什么不精確

          首先,計(jì)算機(jī)是只認(rèn)識(shí)二進(jìn)制的,即0和1,這個(gè)大家一定都知道。

          那么,所有數(shù)字,包括整數(shù)和小數(shù),想要在計(jì)算機(jī)中存儲(chǔ)和展示,都需要轉(zhuǎn)成二進(jìn)制。

          十進(jìn)制整數(shù)轉(zhuǎn)成二進(jìn)制很簡(jiǎn)單,通常采用"除2取余,逆序排列"即可,如10的二進(jìn)制為1010。

          但是,小數(shù)的二進(jìn)制如何表示呢?

          十進(jìn)制小數(shù)轉(zhuǎn)成二進(jìn)制,一般采用"乘2取整,順序排列"方法,如0.625轉(zhuǎn)成二進(jìn)制的表示為0.101。

          但是,并不是所有小數(shù)都能轉(zhuǎn)成二進(jìn)制,如0.1就不能直接用二進(jìn)制表示,他的二進(jìn)制是0.000110011001100… 這是一個(gè)無(wú)限循環(huán)小數(shù)。

          所以,計(jì)算機(jī)是沒(méi)辦法用二進(jìn)制精確的表示0.1的。也就是說(shuō),在計(jì)算機(jī)中,很多小數(shù)沒(méi)辦法精確的使用二進(jìn)制表示出來(lái)。

          那么,這個(gè)問(wèn)題總要解決吧。那么,人們想出了一種采用一定的精度,使用近似值表示一個(gè)小數(shù)的辦法。這就是IEEE 754(IEEE二進(jìn)制浮點(diǎn)數(shù)算術(shù)標(biāo)準(zhǔn))規(guī)范的主要思想。

          IEEE 754規(guī)定了多種表示浮點(diǎn)數(shù)值的方式,其中最常用的就是32位單精度浮點(diǎn)數(shù)和64位雙精度浮點(diǎn)數(shù)。

          在Java中,使用float和double分別用來(lái)表示單精度浮點(diǎn)數(shù)和雙精度浮點(diǎn)數(shù)。

          所謂精度不同,可以簡(jiǎn)單的理解為保留有效位數(shù)不同。采用保留有效位數(shù)的方式近似的表示小數(shù)。

          所以,大家也就知道為什么double表示的小數(shù)不精確了。

          接下來(lái),再回到BigDecimal的介紹,我們接下來(lái)看看是如何表示一個(gè)數(shù)的,他如何保證精確呢?




          BigDecimal如何精確計(jì)數(shù)?

          如果大家看過(guò)BigDecimal的源碼,其實(shí)可以發(fā)現(xiàn),實(shí)際上一個(gè)BigDecimal是通過(guò)一個(gè)"無(wú)標(biāo)度值"和一個(gè)"標(biāo)度"來(lái)表示一個(gè)數(shù)的。

          在BigDecimal中,標(biāo)度是通過(guò)scale字段來(lái)表示的。

          而無(wú)標(biāo)度值的表示比較復(fù)雜。當(dāng)unscaled value超過(guò)閾值(默認(rèn)為L(zhǎng)ong.MAX_VALUE)時(shí)采用intVal字段存儲(chǔ)unscaled value,intCompact字段存儲(chǔ)Long.MIN_VALUE,否則對(duì)unscaled value進(jìn)行壓縮存儲(chǔ)到long型的intCompact字段用于后續(xù)計(jì)算,intVal為空。

          涉及到的字段就是這幾個(gè):

          ?public?class?BigDecimal?extends?Number?implements?Comparable<BigDecimal>?{

          ????????private?final?BigInteger?intVal;

          ????????private?final?int?scale;?

          ????????private?final?transient?long?intCompact;

          ????}

          關(guān)于無(wú)標(biāo)度值的壓縮機(jī)制大家了解即可,不是本文的重點(diǎn),大家只需要知道BigDecimal主要是通過(guò)一個(gè)無(wú)標(biāo)度值和標(biāo)度來(lái)表示的就行了。




          那么標(biāo)度到底是什么呢?

          除了scale這個(gè)字段,在BigDecimal中還提供了scale()方法,用來(lái)返回這個(gè)BigDecimal的標(biāo)度。

          ?/**

          ?????*?Returns?the?scale?of?this?{@code?BigDecimal}.??If?zero

          ?????*?or?positive,?the?scale?is?the?number?of?digits?to?the?right?of

          ?????*?the?decimal?point.??If?negative,?the?unscaled?value?of?the

          ?????*?number?is?multiplied?by?ten?to?the?power?of?the?negation?of?the

          ?????*?scale.??For?example,?a?scale?of?{@code?-3}?means?the?unscaled

          ?????*?value?is?multiplied?by?1000.

          ?????*

          ?????*?@return?the?scale?of?this?{@code?BigDecimal}.

          ?????*/


          ????public?int?scale()?{

          ????????return?scale;

          ????}

          那么,scale到底表示的是什么,其實(shí)上面的注釋已經(jīng)說(shuō)的很清楚了:

          如果scale為零或正值,則該值表示這個(gè)數(shù)字小數(shù)點(diǎn)右側(cè)的位數(shù)。如果scale為負(fù)數(shù),則該數(shù)字的真實(shí)值需要乘以10的該負(fù)數(shù)的絕對(duì)值的冪。例如,scale為-3,則這個(gè)數(shù)需要乘1000,即在末尾有3個(gè)0。

          如123.123,那么如果使用BigDecimal表示,那么他的無(wú)標(biāo)度值為123123,他的標(biāo)度為3。

          而二進(jìn)制無(wú)法表示的0.1,使用BigDecimal就可以表示了,及通過(guò)無(wú)標(biāo)度值1和標(biāo)度1來(lái)表示。

          我們都知道,想要?jiǎng)?chuàng)建一個(gè)對(duì)象,需要使用該類(lèi)的構(gòu)造方法,在BigDecimal中一共有以下4個(gè)構(gòu)造方法:

          ????BigDecimal(int)

          ????BigDecimal(double)?

          ????BigDecimal(long)?

          ????BigDecimal(String)

          以上四個(gè)方法,創(chuàng)建出來(lái)的的BigDecimal的標(biāo)度(scale)是不同的。

          其中 BigDecimal(int)和BigDecimal(long) 比較簡(jiǎn)單,因?yàn)槎际钦麛?shù),所以他們的標(biāo)度都是0。

          而B(niǎo)igDecimal(double) 和BigDecimal(String)的標(biāo)度就有很多學(xué)問(wèn)了。




          BigDecimal(double)有什么問(wèn)題

          BigDecimal中提供了一個(gè)通過(guò)double創(chuàng)建BigDecimal的方法——BigDecimal(double) ,但是,同時(shí)也給我們留了一個(gè)坑!

          因?yàn)槲覀冎溃琩ouble表示的小數(shù)是不精確的,如0.1這個(gè)數(shù)字,double只能表示他的近似值。

          所以,當(dāng)我們使用new BigDecimal(0.1)創(chuàng)建一個(gè)BigDecimal 的時(shí)候,其實(shí)創(chuàng)建出來(lái)的值并不是正好等于0.1的。

          而是0.1000000000000000055511151231257827021181583404541015625。這是因?yàn)閐oule自身表示的只是一個(gè)近似值。

          ?

          所以,如果我們?cè)诖a中,使用BigDecimal(double) 來(lái)創(chuàng)建一個(gè)BigDecimal的話,那么是損失了精度的,這是極其嚴(yán)重的。




          使用BigDecimal(String)創(chuàng)建

          那么,該如何創(chuàng)建一個(gè)精確的BigDecimal來(lái)表示小數(shù)呢,答案是使用String創(chuàng)建。

          而對(duì)于BigDecimal(String) ,當(dāng)我們使用new BigDecimal("0.1")創(chuàng)建一個(gè)BigDecimal 的時(shí)候,其實(shí)創(chuàng)建出來(lái)的值正好就是等于0.1的。

          那么他的標(biāo)度也就是1。

          但是需要注意的是,new BigDecimal("0.10000")和new BigDecimal("0.1")這兩個(gè)數(shù)的標(biāo)度分別是5和1,如果使用BigDecimal的equals方法比較,得到的結(jié)果是false,具體原因和解決辦法參考為什么阿里巴巴禁止使用BigDecimal的equals方法做等值比較?

          那么,想要?jiǎng)?chuàng)建一個(gè)能精確的表示0.1的BigDecimal,請(qǐng)使用以下兩種方式:

          ????BigDecimal?recommend1?=?new?BigDecimal("0.1");

          ????BigDecimal?recommend2?=?BigDecimal.valueOf(0.1);

          這里,留一個(gè)思考題,BigDecimal.valueOf()是調(diào)用Double.toString方法實(shí)現(xiàn)的,那么,既然double都是不精確的,BigDecimal.valueOf(0.1)怎么保證精確呢?




          總結(jié)

          因?yàn)橛?jì)算機(jī)采用二進(jìn)制處理數(shù)據(jù),但是很多小數(shù),如0.1的二進(jìn)制是一個(gè)無(wú)線循環(huán)小數(shù),而這種數(shù)字在計(jì)算機(jī)中是無(wú)法精確表示的。

          所以,人們采用了一種通過(guò)近似值的方式在計(jì)算機(jī)中表示,于是就有了單精度浮點(diǎn)數(shù)和雙精度浮點(diǎn)數(shù)等。

          所以,作為單精度浮點(diǎn)數(shù)的float和雙精度浮點(diǎn)數(shù)的double,在表示小數(shù)的時(shí)候只是近似值,并不是真實(shí)值。

          所以,當(dāng)使用BigDecimal(Double)創(chuàng)建一個(gè)的時(shí)候,得到的BigDecimal是損失了精度的。

          而使用一個(gè)損失了精度的數(shù)字進(jìn)行計(jì)算,得到的結(jié)果也是不精確的。

          想要避免這個(gè)問(wèn)題,可以通過(guò)BigDecimal(String)的方式創(chuàng)建BigDecimal,這樣的情況下,0.1就會(huì)被精確的表示出來(lái)。

          其表現(xiàn)形式是一個(gè)無(wú)標(biāo)度數(shù)值1,和一個(gè)標(biāo)度1的組合。


          往期推薦

          最常見(jiàn)的10種Java異常問(wèn)題!


          try-catch-finally中的4個(gè)巨坑,老程序員也搞不定!


          final的8個(gè)小細(xì)節(jié),聽(tīng)說(shuō)只有高手才知道!你知道幾個(gè)?




          關(guān)注我↓↓↓,每天獲取干貨.
          瀏覽 32
          點(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>
                  五月天自拍视频 | 日日澡综合 | 亚州一区 | 频逼特逼在线视频 | h片免费在线观看视频 |