Java Puzzlers 之Puzzle 2: Time for a Change

Puzzle 2: Time for a Change

考慮下面的問題:

Tom去汽車零件店去買價值1.10元的火花塞,但是在他的錢包都是2美元的票子,如果他用兩美元的票子買那個火花塞,那麼最後能給他找回的零錢是多少呢?

使用用以下的程序來實現,輸出的結果是多少呢?

 

}

 

Solution 2: Time for a Change

很自然的,你可能認爲程序輸出的結果就是0.9 但是程序怎麼知道你想在小數點後面輸出兩位小數呢?如果你知道關於double轉化爲字符串的相關規則(在java-API中,有相關文檔詳細說明了Double.toString)的話,你可以瞭解到程序打印出足夠短的小數去從它最近的相鄰的值區別double類型的值,這個數至少一位小數。看起來很合乎情理,那麼,程序的輸出應該就是0.9 。合乎情理,有時也可能不對。如果你運行此程序,你就會發現結果是0.8999999999999999

問題在於數字1.1 不能正確表示爲double,因此它用最接近的double值來表示。在程序中用2減去這個值。不幸的是,結果並不是最接近0.9double值。 最接近結果的值就是在運行上面程序後輸出的可怕的數字。

通常情況下,問題在於:並不是所有的數字都可以使用浮點數來完全正確表示。如果你使用5.0以及其後的版本,你就很輕鬆的使用printf來修正這個錯誤,能得到完全精確的值:

 

 

輸出的結果正確,但是並不代表從根本上解決問題:它仍然使用double二進制浮點數算法。浮點數算法提供了很好的廣域的值匹配而不是完全的精確結果。二進制浮點計算是很不適合於錢方面的計算。因爲它不可能表示0.1或者其他的10的負數冪(特別是有限二進制小數)。

解決方法之一就是使用整數,如int 或者long,使用美分來進行結算。如果使用這樣的結算方法,首先必須確保你的整數足夠大,以致於能表示這些數。對於上面這個問題,整數完全足夠。現在我們使用println來重新結算,使用int值來表示美分值得運算,這個版本中,輸出的就是90美分,這個結果就是正確的:

 

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

 

另外一個解決的辦法就是使用BigDecimal,這個可以進行精確的計算。這個也是SQL中的DECEMAL類型經由JDBC轉爲而成的類型。這裏有一個警告:總是使用BigDecimalString)構造函數,而不要使用BigDecimaldouble)。後者將會創建參數的一個“準確”的值:new BigDecimal(.1)將會返回0.1000000000000000055511151231257827021181583404541015625。正確的使用BigDecimal,程序就會輸出正確的結果:0.9

 

 

}

 

這個版本也比較簡單,就像Java沒有提供BigDecimal的語言上的支持一樣。使用BigDecimal計算速度也比使用原始的類型計算慢,這導致如果大量的使用小數計算時有可能是個問題。對大部分程序來說並沒有影響。

總的來說,在需要很精確的結果實,避免使用float以及double 對於錢方面的計算,使用intlong、或者BigDecimal。對於語言設計者們,需要考慮提供小數算法的很好語言上的支持。一種方法就是提供有限的運算符重載操作,這樣就可以使用運算符號來進行數字類型計算,就像BigDecimal那樣。另外一種方法就是像COBOL以及PL/L那樣提供原始小數類型。

 

 

 

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