面試官問我:你確定用了BigDecimal後,計算結果一定精確?

前言

過年了,年終獎也領完了,這不打算出去面試一波,看看自己在市場中的價值,於是我簡單地做了波簡歷,然後去面試一波,結果誰知,第一個面試就差點碰壁了,面試官竟然問我BigDecimal這個類,可是我不慌,心中有料,內心不慌,於是輕鬆拿下了一波高薪offer

BigDecimal,這個類其實對於經常接觸金融、電商、支付的猿猿來說不算陌生,我也還算是熟悉,我也經常用,但是很多時候我們只知道他的用法,並不知道他還有隱藏的細節

首先,這是java。math包中提供的一種可以用來進行更高精度運算的型別,相較於double、float這些型別來說,BigDecimal在和金額計算打交道應該說有著天然的優勢,這個大家也很熟悉了,接下來我們一起來分析下BigDecimal中的哪些注意事項

1、BigDecimal不能使用equals方法做等值比較

2、BigDecimal使用double初始化時存在精度風險

面試官問我:你確定用了BigDecimal後,計算結果一定精確?

問題一

這個問題其實真的是很細節了,不知道大家有沒有注意到,在《阿里巴巴Java開發手冊》中其實也有註明

面試官問我:你確定用了BigDecimal後,計算結果一定精確?

不知道你們在比較BigDecimal的時候都是怎麼使用的,但是千萬不要用==這種方式來使用哦,這個應該不用多說吧,BigDecimal屬於物件,不是基本型別,不能用==來比較

一般說到這裡,大家就知道了,物件的話肯定使用equals來進行比較咯,這樣就沒問題了,告訴你,用equals比較也有問題

你個渣,我懷疑你在騙我,那你告訴我為何,還有怎麼解決?

那我該如何比較呢,自定義個類,繼承BigDecimal,重寫equals,當然可以。但是其實有更好的辦法,在BigDecimal內部提供了compareTo方法買這個方法可以直接判斷兩個數字的值,相等則返回0

知其然,也要知其所以然,我肯定會解釋清楚的嘞

我們來看個例子:

BigDecimal bigDecimal1 = new BigDecimal(1); BigDecimal bigDecimal2 = new BigDecimal(1); System。out。println(bigDecimal1。equals(bigDecimal2)); BigDecimal bigDecimal3 = new BigDecimal(1); BigDecimal bigDecimal4 = new BigDecimal(1。0); System。out。println(bigDecimal3。equals(bigDecimal4)); BigDecimal bigDecimal5 = new BigDecimal(“1”); BigDecimal bigDecimal6 = new BigDecimal(“1。0”); System。out。println(bigDecimal5。equals(bigDecimal6));

以上程式碼輸出結果:true true false

有的時候結果是true,有的時候結果卻是false,很奇怪,為什麼呢?我們來看下BigDecimal的equals的原始碼:

public boolean equals(Object x) { if (!(x instanceof BigDecimal)) return false; BigDecimal xDec = (BigDecimal) x; if (x == this) return true; if (scale != xDec。scale) return false; long s = this。intCompact; long xs = xDec。intCompact; if (s != INFLATED) { if (xs == INFLATED) xs = compactValFor(xDec。intVal); return xs == s; } else if (xs != INFLATED) return xs == compactValFor(this。intVal); return this。inflated()。equals(xDec。inflated());}

裡面有一個scale標度的比較,大概這就是為什麼bigDecimal5和bigDeclmal6的比較結果是false的原因了。equals不僅會比較數值,還會比較這個標度是否一樣

標度問題

使用equals進行比較的時候會比較數值大小和scale標度問題,那為什麼上面的bigDecimal1和2、bigDecimal3和4卻是相同的呢,難道是因為他們的型別是int、long,而bigDecimal5和6的型別是string,導致出現精度問題?

BigDecimal有四種定義的型別,包括int、long、double、String四種,首先int和long型別都是整數,標度都是0。當型別是double的時候,new Bigdecimal(double) => new BigDecimale(0。1),實際傳入的是0。1000000000000000055511151231527827021181583404541015625,這個時候的標度就是55,也就是小數點的個數。

而對於 new bigDecimal(1。0)來說,實際上就是整數,也就是不存在後綴,所以和整數的標度大小是一樣的

對於BigDecimal(String)來說,當我們傳入一個字串的時候,new BigDecimal(“0。1”)建立一個BigDecimal的時候,其實創建出來的值正好就是等於0。1的,那麼他的標題也就是1。如果使用的是new BigDecimal(“0。10000”),此時標度就是5,所以這也就是解釋了為什麼最後的bigDecimal5和6的結果不一樣咯

那如何解決呢?其實BigDecimal不僅提供了equals方法,還提供了一個compareTo()方法,這個方法其實就是隻比較兩個數值的大小,感興趣的可以去研究研究

面試官問我:你確定用了BigDecimal後,計算結果一定精確?

問題二

BigDecimal使用double初始化時存在精度風險,那這是怎麼一回事呢?其實在阿里開發手冊中也有這麼一條建議,或者說是要求吧

面試官問我:你確定用了BigDecimal後,計算結果一定精確?

禁止使用構造方法BigDecimal(double)的方式把double值轉化成BigDecimal物件

我們知道,計算機是隻認識二進位制的,只認識0和1,也就是說任何資料都會轉化成0和1儲存在計算機中,整數簡單,除二取餘,逆序排列即可。而小數則不一定全部能轉化成二進位制,比如0。1,在轉換的過程中會出現迴圈的情況,所以這種是無法正確的儲存完整的資料的,計算機是無法精確的儲存這種資料的,所以計算機採用的是一定的精度來解決這個問題的,這就是IEEE 754(IEEE二進位制浮點數算術標準)規範的主要思想。

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

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

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

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 { private final BigInteger intVal; private final int scale; private final transient long intCompact; }

大家只需要知道BigDecimal主要是透過一個無標度值和標度來表示的就行了。

那麼標度到底是什麼呢?除了scale這個欄位,在BigDecimal中還提供了scale()方法,用來返回這個BigDecimal的標度。那麼,scale到底表示的是什麼,其實上面的註釋已經說得很清楚了:

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

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

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

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

其中 BigDecimal(int)和BigDecimal(long) 比較簡單,因為都是整數,所以他們的標度都是0。而BigDecimal(double) 和BigDecimal(String)的標度就有很多學問了。

BigDecimal(double)有什麼問題

BigDecimal中雖然提供了一個透過double建立BigDecimal的方法,但是這其中也挖下了一個坑

我們知道,double表示的小數是不精確的,比如0。1這個數值,double只能表示他的近似值,所以當我們使用new BigDecimal(0。1)的時候,實際上創建出來的數值並不是正好等於0。1的,而是一個近似值

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

那麼,該如何建立一個精確的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,可以使用compareTo方法進行比較

那麼,想要建立一個能精確的表示0。1的BigDecimal,請使用以下兩種方式:

BigDecimal recommend1 = new BigDecimal(“0。1”); BigDecimal recommend2 = BigDecimal。valueOf(0。1);

面試官問我:你確定用了BigDecimal後,計算結果一定精確?

面試官問我:你確定用了BigDecimal後,計算結果一定精確?

求贊

好了,以上就是全部內容了,我是小仙人,你們的學習成長小夥伴

面試官問我:你確定用了BigDecimal後,計算結果一定精確?

我希望有一天能夠靠寫字養活自己,現在還在磨練,這個時間可能會有很多年,感謝你們做我最初的讀者和傳播者。請大家相信,只要給我一份愛,我終究會還你們一頁情的。

再次感謝大家能夠讀到這裡,

我後面會持續的更新技術文章以及一些記錄生活的靈魂文章,如果覺得不錯的話,覺得【

小仙

】有點東西的話,求點贊、關注、分享三連

哦,對了!後續的更新文章我都會及時放到這裡,歡迎大家點選觀看,都是乾貨文章啊,建議收藏,以後隨時翻閱檢視

https://github。com/DayuMM2021/Java