天天看點

找零時刻

請考慮下面這段話所描述的問題:  

Tom 在一家汽車配件商店購買了一個價值$1.10 的火花塞,但是他錢包中都是兩

美元一張的鈔票。如果他用一張兩美元的鈔票支付這個火花塞,那麼應該找給他

多少零錢呢?  

下面是一個試圖解決上述問題的程式,它會列印出什麼呢?  

public class Change{ 

        public static void main(String args[]){ 

                 System.out.println(2.00 - 1.10); 

        } 

你可能會很天真地期望該程式能夠列印出 0.90,但是它如何才能知你想要打

印小數點後兩位小數呢?  

如果你對在Double.toString 文檔中所設定的将 double類型的值轉換為字元串

的規則有所了解,你就會知                 該程式列印出來的小數,是足以将double類型的

值與最靠近它的臨近值區分出來的最短的小數,它在小數點之前和之後都至少有

一位。是以,看起來,該程式應該列印 0.9 是合理的。  

這麼分析可能顯得很合理,但是并不正确。如果你運作該程式,你就會發現它打

印的是0.8999999999999999。  

問題在于1.1 這個數字不能被精确表示成為一個double,是以它被表示成為最

接近它的 double 值。該程式從 2 中減去的就是這個值。遺憾的是,這個計算的

結果并不是最接近 0.9 的 double 值。表示結果的double 值的最短表示就是你所

看到的列印出來的那個可惡的數字。  

更一般地說,問題在于并不是所有的小數都可以用二進制浮點數來精确表示的。  

如果你正在用的是JDK 5.0 或更新的版本,那麼你可能會受其誘惑,通過使用

printf 工具來設定輸出精度的方訂正該程式:    

//拙劣的解決方案——仍舊是使用二進制浮點數 

System.out.printf("%.2f%n",2.00 - 1.10); 

這條語句列印的是正确的結果,但是這并不表示它就是對底層問題的通用解決方

案:它使用的仍舊是二進制浮點數的 double 運算。浮點運算在一個範圍很廣的

值域上提供了很好的近似,但是它通常不能産生精确的結果。二進制浮點對于貨

币計算是非常不适合的,因為它不可能将0.1——或者 10的其它任何次負幂——

精确表示為一個長度有限的二進制小數  

解決該問題的一種方式是使用某種整數類型,例如 int 或 long,并且以分為單

位來執行計算。如果你采納了此路線,請確定該整數類型大到足夠表示在程式中

你将要用到的所有值。對這裡舉例的謎題來說,int 就足夠了。下面是我們用 int

類型來以分為機關表示貨币值後重寫的println 語句。這個版本将列印出正确答

案90 分:  

System.out.println((200 - 110) + "cents"); 

解決該問題的另一種方式是使用執行精确小數運算的BigDecimal。它還可以通

過 JDBC 與SQL DECIMAL 類型進行互操作。這裡要告誡你一點: 一定要用

BigDecimal(String)構造器,而千萬不要用BigDecimal(double)。後一個構造

器将用它的參數的 “精确”值來建立一個執行個體:new BigDecimal(.1)将傳回一個

表示 0.100000000000000055511151231257827021181583404541015625 的

BigDecimal。通過正确使用BigDecimal,程式就可以列印出我們所期望的結果

0.90:  

import java.math.BigDecimal; 

public class Change1{ 

        public static void main(String args[]){ 

                 System.out.println(new BigDecimal("2.00"). 

                 subtract(new BigDecimal("1.10"))); 

        } 

這個版本并不是十分地完美,因為Java 并沒有為BigDecimal 提供任何語言上的

支援。使用BigDecimal 的計算很有可能比那些使用原始類型的計算要慢一些,

對某些大量使用小數計算的程式來說,這可能會成為問題,而對大多數程式來說,

這顯得一點也不重要。  

總之, 在需要精确答案的地方,要避免使用float 和 double;對于貨币計算,

要使用 int、long 或 BigDecimal。對于語言設計者來說,應該考慮對小數運算

提供語言支援。一種方式是提供對操作符重載的有限支援,以使得運算符可以被

塑造為能夠對數值引用類型起作用,例如BigDecimal。另一種方式是提供原始

的小數類型,就像 COBOL 與PL/I 所作的一樣。  

内容來至<<java解惑>>