解釋BigDecimal精度的坑

問題重現
BigDecimal b1 = new BigDecimal(0.1);
BigDecimal b2 = new BigDecimal(0.5);
System.out.println("b1="+b1+"\nb2="+b2);

---------------結果----------------------
b1=0.1000000000000000055511151231257827021181583404541015625
b2=0.5
1
2
3
4
5
6
7
爲什麼b1的結果是後面有一大串數字,而b2卻是正確的呢?

分析
1. effective java 第48條解釋:
如果需要精確的答案,請避免使用float和double
float和double類型主要是爲了科學計算和工程計算而設計,他們執行二進制浮點運算,這是爲了在廣泛的數值範圍上提供較爲精確的快速近和計算而精心設計的,然而,他們並沒有提供完全精確的結果,所以不應該被用於精確的結果的場合

2.個人分析:
從effective java中,明確的說了,float和double是不精確的運算,他們只是爲了廣泛的運算保證有一個相近的值
爲什麼要使用二進制計算double,float和double他們執行的二進制浮點運算,因爲使用二進制計算效率要高,適用於日常運算,而二進制也能給我提供一個相對準確的值
爲什麼二進制運算不能提供準確的值,其實不能說二進制不能提供準確的值,而是二進制不能準確的表示一個小數,就像十進制不能準確的表示1/3,1/6等。
爲什麼十進制不能表示1/3 ,因爲1/3=0.333333333333333。。。。。,我們始終不能說清楚這後面有多少個3,所以說十進制表示不了1/3.
爲什麼二進制不能表示一個小數,舉例0.1換成二進制,請看下面的運算

0.1 * 2 = 0.2     -----0
0.2 * 2 = 0.4     -----0
0.4 * 2 = 0.8     -----0
0.8 * 2 = 1.6     -----1
0.6 * 2 = 1.2     -----1
0.2 * 2 = 0.4     -----0
.
.
.
//你懂的 0.0001100110011001100110011001100110011001100110011001101
1
2
3
4
5
6
7
8
9
10
所以二進制表示不了0.1,但是有的小數卻可以表示,就像十進制能表示1/2一樣,請看0.5

0.5 * 2 = 1     -----1
//所以0.5用二進制表示就是0.1
1
2
這也就解釋了,我們遇到的坑,new BigDecimal(0.1);返回一個錯誤的值,而 new BigDecimal(0.5);返回一個準確的值,從本質上講0.1,只要他的類型是double或者float,它本身就一個不準確的值,這其實跟BigDecimal無關。我們使用new BigDecimal(Double d);這個構造時,假如入參是0.1,這其中會將0.1轉成Double,而此時0.1的值就已經不準確了,比如以下代碼

System.out.println(0.5*3);
System.out.println(0.1*3);
//這兩句代碼,可能很清楚說明二進制不能表示0.1,可以表示0.5
1
2
3
如果你還沒清楚這個坑的來歷,請執行這兩句代碼,看看結果,帶着結果重新閱讀一遍我的分析

解決辦法
既然知道了坑的來歷,我們就只要避免這個坑就行了,這個坑說到底是double或者float造成的,那麼我們在構造BigDecimal的對象時,不用這兩個構造方法就行了,如:

//使用String構造
BigDecimal b1 = new BigDecimal("0.1");
//或者是:
BigDecimal b1 = BigDecimal.valueOf(0.1);
//點開valueOf的源碼,可以看到在源碼中也是用new BigDecimal(String);返回一個BigDecimal對象的
//源碼如下:
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));
}
————————————————
版權聲明:本文爲CSDN博主「far.liu」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/gege87417376/java/article/details/79550749

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章