【超詳細】解釋BigDecimal精度的坑

far.liu發表於2018-03-20
看到這篇文章的,想必是遇到同樣的坑,那麼請慢慢的看下去

問題重現

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

為什麼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

所以二進位制表示不了0.1,但是有的小數卻可以表示,就像十進位制能表示1/2一樣,請看0.5

0.5 * 2 = 1     -----1
//所以0.5用二進位制表示就是0.1

這也就解釋了,我們遇到的坑,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

如果你還沒清楚這個坑的來歷,請執行這兩句程式碼,看看結果,帶著結果重新閱讀一遍我的分析

解決辦法

既然知道了坑的來歷,我們就只要避免這個坑就行了,這個坑說到底是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));
}

相關文章