1. 程式人生 > >[紙上談兵]BigDecimal原始碼學習

[紙上談兵]BigDecimal原始碼學習

一、BigDecimal宣告
新來同事看到我們程式碼中BigDeciaml用法,感覺比較奇怪,由此引出了這篇文章

使用下面方式宣告BigDecimal時,會出現精度問題
BigDecimal bd3 = new BigDecimal(0.1D);

推薦用法
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = BigDecimal.valueOf(1D);

用以上方法就不會出問題精度問題
BigDecimal.valueOf() 檢視原始碼就可以知道,也是使用的new BigDecimal("0.1")建構函式


注意:

BigDecimal都是不可變的(immutable)的,在進行每一步運算時,都會產生一個新的物件,所以在做加減乘除運算時千萬要儲存操作後的值。



二、為什麼BigDecimal使用double或float會出現精度問題?

原因:十進位制轉二進位制的演算法導致轉為二進位制時,某些數字會永遠不為零


十進位制轉二進位制方法
整數部分:除以2,取出餘數,商繼續除以2,直到得到0為止,將取出的餘數逆序
小數部分:乘以2,然後取出整數部分,將剩下的小數部分繼續乘以2,然後再取整數部分,一直取到小數部分為零為止。如果永遠不為零,則按要求保留足夠位數的小數,最後一位做0舍1入。將取出的整數順序排列

看了這個我想大家就都明白了,double或float之類的浮點數,轉為二進位制儲存時某些數字會永遠不為零

十進位制轉二進位制例子:
整數部分: 我相信你會,你可以的。不會可以看我下面的參考資料
小數部分:乘以2,取整,小數部分繼續乘以2,取整,得到小數部分0為止,將整數順序排列
0.1x2=0.2 取整0,小數部分是2
0.2x2=0.4 取整0,小數部分是4
0.4x2=0.8 取整0,小數部分是8
0.8x2=1.6 取整1,小數部分是6
0.6x2=1.2 取整1,小數部分是2
。。。。。你會發出與前面重複了,會一直不停迴圈下去

三、為什麼BigDecimal使用String不會出現精度問題
現在我們明白為什麼用double,float 出現出精度問題。現在我們要看一下BigDecimal對String做了什麼不會出現精度問題


首先: 程式就是資料結構與演算法

通過Debug構造方法,我們可以瞭解,BigDecimal底層資料結構主要是由下面四個屬性值組成.
int scale; //有多少位小數(即小數點後有多少位)
int precision; //總工有多少位數字
long intCompact; //字串去掉小數點後,轉為long的值,只有當傳的字串長度小於18時才使用該言
BigInteger intVal; //當傳的字串長度大於等於18時才使用BigInteger表示數字
以new BigDecimal("12.12")為例
scale值為2
precision值為4
intCompact值為1212

intVal值為空。之所以為空是因為字串長度沒有超18位,所以不啟用BigInteger表示

看到這進而其實大家應該明白了,BigDecimal將String轉為了long或BigInteger來進行計算。


四、使用Idea Debug BigDeciaml原始碼出現的奇怪現象
程式碼:
BigDecimal bd1 = new BigDecimal("1");
System.out.println("bd1:" + bd1);

上面的程式碼,不進行debug列印的結果是"bd1:1"
我們在Idea中同時在BigDeciaml類的411行打上斷點(程式碼:if (offset + len > in.length || offset < 0) ,並非一定在這裡打上斷點,其實只要是在原始碼中加上斷點能讓這段程式碼走到debug即可,但不要在toString方法上加),然後debug執行,到了點斷處,可以直接往下走,讓程式碼執行完,你會發現結果是"bd1:0"。
原因是什麼我還不明白,估計是idea的debug存在bug導致該問題產生  


五、TODO:

   加、減、乘、除,每個具體原始碼如何實現



參考:
https://blog.csdn.net/nesll/article/details/52302520