日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

你以为用了BigDecimal后,计算结果就一定精确了?

發布時間:2025/3/11 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 你以为用了BigDecimal后,计算结果就一定精确了? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

BigDecimal,相信對于很多人來說都不陌生,很多人都知道他的用法,這是一種java.math包中提供的一種可以用來進行精確運算的類型。

很多人都知道,在進行金額表示、金額計算等場景,不能使用double、float等類型,而是要使用對精度支持的更好的BigDecimal。

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

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

除了這個情況,BigDecimal的使用的第一步就是創建一個BigDecimal對象,如果這一步都有問題,那么后面怎么算都是錯的!

那到底應該如何正確的創建一個BigDecimal?

關于這個問題,我Review過很多代碼,也面試過很多一線開發,很多人都掉進坑里過。這是一個很容易被忽略,但是又影響重大的問題。

關于這個問題,在《阿里巴巴Java開發手冊》中有一條建議,或者說是要求:

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

想要搞清楚這個問題,主要需要弄清楚以下幾個問題:

1、為什么說double不精確?

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

在知道這兩個問題的答案之后,我們也就大概知道為什么不能使用BigDecimal(double)來創建一個BigDecimal了。

double為什么不精確

首先,計算機是只認識二進制的,即0和1,這個大家一定都知道。

那么,所有數字,包括整數和小數,想要在計算機中存儲和展示,都需要轉成二進制。

十進制整數轉成二進制很簡單,通常采用"除2取余,逆序排列"即可,如10的二進制為1010。

但是,小數的二進制如何表示呢?

十進制小數轉成二進制,一般采用"乘2取整,順序排列"方法,如0.625轉成二進制的表示為0.101。

但是,并不是所有小數都能轉成二進制,如0.1就不能直接用二進制表示,他的二進制是0.000110011001100… 這是一個無限循環小數。

所以,計算機是沒辦法用二進制精確的表示0.1的。也就是說,在計算機中,很多小數沒辦法精確的使用二進制表示出來。

那么,這個問題總要解決吧。那么,人們想出了一種采用一定的精度,使用近似值表示一個小數的辦法。這就是IEEE 754(IEEE二進制浮點數算術標準)規范的主要思想。

IEEE 754規定了多種表示浮點數值的方式,其中最常用的就是32位單精度浮點數和64位雙精度浮點數。

在Java中,使用float和double分別用來表示單精度浮點數和雙精度浮點數。

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

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

接下來,再回到BigDecimal的介紹,我們接下來看看是如何表示一個數的,他如何保證精確呢?

BigDecimal如何精確計數?

如果大家看過BigDecimal的源碼,其實可以發現,實際上一個BigDecimal是通過一個"無標度值"和一個"標度"來表示一個數的。

在BigDecimal中,標度是通過scale字段來表示的。

而無標度值的表示比較復雜。當unscaled value超過閾值(默認為Long.MAX_VALUE)時采用intVal字段存儲unscaled value,intCompact字段存儲Long.MIN_VALUE,否則對unscaled value進行壓縮存儲到long型的intCompact字段用于后續計算,intVal為空。

涉及到的字段就是這幾個:

?public?class?BigDecimal?extends?Number?implements?Comparable<BigDecimal>?{private?final?BigInteger?intVal;private?final?int?scale;?private?final?transient?long?intCompact;}

關于無標度值的壓縮機制大家了解即可,不是本文的重點,大家只需要知道BigDecimal主要是通過一個無標度值和標度來表示的就行了。

那么標度到底是什么呢?

除了scale這個字段,在BigDecimal中還提供了scale()方法,用來返回這個BigDecimal的標度。

?/***?Returns?the?<i>scale</i>?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到底表示的是什么,其實上面的注釋已經說的很清楚了:

如果scale為零或正值,則該值表示這個數字小數點右側的位數。如果scale為負數,則該數字的真實值需要乘以10的該負數的絕對值的冪。例如,scale為-3,則這個數需要乘1000,即在末尾有3個0。

如123.123,那么如果使用BigDecimal表示,那么他的無標度值為123123,他的標度為3。

而二進制無法表示的0.1,使用BigDecimal就可以表示了,及通過無標度值1和標度1來表示。

我們都知道,想要創建一個對象,需要使用該類的構造方法,在BigDecimal中一共有以下4個構造方法:

????BigDecimal(int)BigDecimal(double)?BigDecimal(long)?BigDecimal(String)

以上四個方法,創建出來的的BigDecimal的標度(scale)是不同的。

其中 BigDecimal(int)和BigDecimal(long) 比較簡單,因為都是整數,所以他們的標度都是0。

而BigDecimal(double) 和BigDecimal(String)的標度就有很多學問了。

BigDecimal(double)有什么問題

BigDecimal中提供了一個通過double創建BigDecimal的方法——BigDecimal(double) ,但是,同時也給我們留了一個坑!

因為我們知道,double表示的小數是不精確的,如0.1這個數字,double只能表示他的近似值。

所以,當我們使用new BigDecimal(0.1)創建一個BigDecimal 的時候,其實創建出來的值并不是正好等于0.1的。

而是0.1000000000000000055511151231257827021181583404541015625。這是因為doule自身表示的只是一個近似值。

所以,如果我們在代碼中,使用BigDecimal(double) 來創建一個BigDecimal的話,那么是損失了精度的,這是極其嚴重的。

使用BigDecimal(String)創建

那么,該如何創建一個精確的BigDecimal來表示小數呢,答案是使用String創建。

而對于BigDecimal(String) ,當我們使用new BigDecimal("0.1")創建一個BigDecimal 的時候,其實創建出來的值正好就是等于0.1的。

那么他的標度也就是1。

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

那么,想要創建一個能精確的表示0.1的BigDecimal,請使用以下兩種方式:

????BigDecimal?recommend1?=?new?BigDecimal("0.1");BigDecimal?recommend2?=?BigDecimal.valueOf(0.1);

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

總結

因為計算機采用二進制處理數據,但是很多小數,如0.1的二進制是一個無線循環小數,而這種數字在計算機中是無法精確表示的。

所以,人們采用了一種通過近似值的方式在計算機中表示,于是就有了單精度浮點數和雙精度浮點數等。

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

所以,當使用BigDecimal(Double)創建一個的時候,得到的BigDecimal是損失了精度的。

而使用一個損失了精度的數字進行計算,得到的結果也是不精確的。

想要避免這個問題,可以通過BigDecimal(String)的方式創建BigDecimal,這樣的情況下,0.1就會被精確的表示出來。

其表現形式是一個無標度數值1,和一個標度1的組合。

往期推薦

最常見的10種Java異常問題!


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


final的8個小細節,聽說只有高手才知道!你知道幾個?


關注我↓↓↓,每天獲取干貨.

總結

以上是生活随笔為你收集整理的你以为用了BigDecimal后,计算结果就一定精确了?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。