來考慮這樣一種情況,先來看代碼:
public static void main(String[] args){
System.out.println(0.4 + 0.8); // = 1.2 ?
System.out.println(2 - 1.1); // = 0.9 ?
System.out.println(0.2 * 3); // = 0.6 ?
System.out.println(1.2 / 3); // = 0.4 ?
}
也許你會天真的認為,第一行打印 1.2,第二行打印 0.9,第三行打印 0.6,第四行打印 0.4,因為依據(jù)多年的數(shù)學(xué)慣性邏輯輸出結(jié)果與預(yù)期的一致是很理所當(dāng)然的事情嘛!
但是當(dāng)程序跑完之后,輸出的結(jié)果與預(yù)期的大有出入,來看下后臺打印的結(jié)果:
1.2000000000000002
0.8999999999999999
0.6000000000000001
0.39999999999999997
結(jié)果看到這樣的結(jié)果,是不是很讓人郁悶?zāi)???dāng)然了,這些數(shù)據(jù)是我故意挑的,并不是所有涉及浮點數(shù)的運算操作都會算出這樣預(yù)期之外的結(jié)果,但是一件很明了的事情就是,
當(dāng)操作涉及浮點數(shù)運算的時候,我們一定要謹防這樣的事情發(fā)生。
上面代碼中,加也好,減也好,乘也好,除也好,它們都是屬于 double 級別的運算,那為什么會打印輸出這樣的結(jié)果呢?原因是,并不是所有的浮點數(shù)都能夠被精確的表示成一個
double 類型值,有些浮點數(shù)值不能夠被精確的表示成 double 類型值,因此它會被表示成與它最接近的 double 類型的值。所以很遺憾,0.4 + 0.8 ≠ 1.2 。……
怎么來解決這個問題呢?在 java 中提供了一個 BigDecimal 類來執(zhí)行精確小數(shù)的計算,BigDecimal 類提供了以下操作:算術(shù)、標度操作、舍入、比較、哈希算法和格式轉(zhuǎn)換。
BigDecimal 類提供的方法:
加法:add
減法:subtract
乘法:multiply
除法:divide
…… ……
BigDecimal 類提供的更多的方法請自行查看 API,下面用 BigDecimal 類改寫上面的代碼實現(xiàn):
public static void main(String[] args){
System.out.println(new BigDecimal(0.4).add(new BigDecimal(0.8))); // = 1.2 ?
System.out.println(new BigDecimal(2).subtract(new BigDecimal(1.1))); // = 0.9 ?
System.out.println(new BigDecimal(0.2).multiply(new BigDecimal(3))); // = 0.6 ?
System.out.println(new BigDecimal(1.2).divide(new BigDecimal(3))); // = 0.4 ?
}
也許你正在查類 BigDecimal 的 API,API 上對類 BigDecimal 有一大串的文字說明,也許你還沒來得及看完,但能夠確定的是,類 BigDecimal 確實能夠準確保證精確小數(shù)的執(zhí)行,
那我上面代碼的注釋是不是忘記去掉了啊?不是。要是真這么干,那就大禍了。先來看一下后臺的打印輸出結(jié)果:
1.20000000000000006661338147750939242541790008544921875
0.899999999999999911182158029987476766109466552734375
0.600000000000000033306690738754696212708950042724609375
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1603)
at example.BigDecimalApp.main(BigDecimalApp.java:20)
如果真這么玩了,你會看到結(jié)果更惡心了,這還不算,而且還拋了異常,這是為什么呢?別急,來看一下 API 上是怎么說的:
“
public BigDecimal(double val)
將 double 轉(zhuǎn)換為 BigDecimal,后者是 double 的二進制浮點值準確的十進制表示形式。返回的 BigDecimal 的標度是使 (10scale × val) 為整數(shù)的最小值。
注:
1. 此構(gòu)造方法的結(jié)果有一定的不可預(yù)知性。有人可能認為在 Java 中寫入 new BigDecimal(0.1) 所創(chuàng)建的 BigDecimal 正好等于 0.1(非標度值 1,其標度為 1),但是它實際上等于
0.1000000000000000055511151231257827021181583404541015625。這是因為 0.1 無法準確地表示為 double (或者說對于該情況,不能表示為任何有限長度的二進制小數(shù))
這樣,傳入 到構(gòu)造方法的值不會正好等于 0.1(雖然表面上等于該值)。
2. 另一方面,String 構(gòu)造方法是完全可預(yù)知的:寫入 new BigDecimal("0.1") 將創(chuàng)建一個 BigDecimal,它正好 等于預(yù)期的 0.1。因此,比較而言,通常建議優(yōu)先使用 String 構(gòu)造方法
3. 當(dāng) double 必須用作 BigDecimal 的源時,請注意,此構(gòu)造方法提供了一個準確轉(zhuǎn)換;它不提供與以下操作相同的結(jié)果:先使用 Double.toString(double) 方法,
然后使用 BigDecimal(String) 構(gòu)造方法,將 double 轉(zhuǎn)換為 String。要獲取該結(jié)果,請使用 static valueOf(double) 方法。
參數(shù):
val - 要轉(zhuǎn)換為 BigDecimal 的 double 值。
拋出:
NumberFormatException - 如果 val 為無窮大或 NaN。
”
以上文字摘自 API,API 上解釋的很清楚了,這里就不多說了,API 建議優(yōu)先使用 String 構(gòu)造方法,那我們就來試一下唄:
public static void main(String[] args){
System.out.println(new BigDecimal("0.4").add(new BigDecimal("0.8"))); // = 1.2 √
System.out.println(new BigDecimal("2").subtract(new BigDecimal("1.1"))); // = 0.9 √
System.out.println(new BigDecimal("0.2").multiply(new BigDecimal("3"))); // = 0.6 √
System.out.println(new BigDecimal("1.2").divide(new BigDecimal("3"))); // = 0.4 √
}
后臺打印輸出結(jié)果:
1.2
0.9
0.6
0.4
OK,這下子終于不出簍子了,所以千萬不能隨隨便便使用 BigDecimal(double) 構(gòu)造器來創(chuàng)建 BigDecimal 對象,因為該構(gòu)造器是根據(jù)它的參數(shù)的精確值來創(chuàng)建實例對象的,
該構(gòu)造方法的結(jié)果還是有一定的不可預(yù)知性,用 BigDecimal(String) 此構(gòu)造器來創(chuàng)建 BigDecimal 實例那就不會有問題了。
以上提到了類 BigDecimal 中的 add 、subtract 、multiply 、divide 方法,在 API 中,你可以看到,這幾個方法都各自有自己的一個重載方法,如:
add (BigDecimal augend, MathContext mc) ……
第二個參數(shù) mc 是什么意思呢?先來看一段代碼:
public static void main(String[] args){
//計算結(jié)果保留兩位有效數(shù)字
System.out.println(new BigDecimal(Math.PI + "").add(new BigDecimal("0.89842"),new MathContext(2))); //輸出 4.0
}
第二個參數(shù) mc 是用來保留計算結(jié)果的有效位數(shù)的,其他三個方法的重載用法是一樣的,這里就不一 一列出來了。