Tips
書中的源代碼地址:https://github.com/jbloch/effective-java-3e-source-code
注意,書中的有些代碼裏方法是基於Java 9 API中的,因此JDK 最好下載 JDK 9以上的版本。java
float和double類型主要用於科學和工程計算。 它們執行二進制浮點運算,通過精心設計,可在很寬的範圍內快速提供準確的近似值。 可是,它們不能提供準確的結果,不該在須要確切結果的地方使用。 float和double類型特別不適合進行貨幣計算,由於不可能將0.1(或任何其餘10的負次方)精確地表示爲float或double。git
例如,假設你的口袋裏有1.03美圓,花了42美分。 你還剩多少錢? 如下是試圖回答這個問題的天真的代碼片斷:github
System.out.println(1.03 - 0.42);
不幸的是,它輸出了0.6100000000000001。這不是個例。假設你口袋裏有一美圓,你買了9墊圈,每一個10美分。你還剩多少零錢?性能
System.out.println(1.00 - 9 * 0.10);
根據這個程序片斷,能夠獲得0.0999999999999999998美圓。設計
你可能認爲,只需在打印以前將結果四捨五入就能夠解決這個問題,但不幸的是,這種方法並不老是有效。例如,假設你口袋裏有一美圓,你看到一個貨架上有一排好吃的糖果,它們的價格僅僅是10美分,20美分,30美分,以此類推,直到1美圓。你每買一顆糖,從10美分的那顆開始,直到你買不起貨架上的下一顆糖。你買了多少糖果,換了多少零錢?這裏有一個簡單的程序設計來解決這個問題:code
// Broken - uses floating point for monetary calculation! public static void main(String[] args) { double funds = 1.00; int itemsBought = 0; for (double price = 0.10; funds >= price; price += 0.10) { funds -= price; itemsBought++; } System.out.println(itemsBought + " items bought."); System.out.println("Change: $" + funds); }
若是你運行該程序,會發現你能夠買三塊糖果,剩下0.3999999999999999美圓。 這是錯誤的答案! 解決此問題的正確方法是使用BigDecimal,int或long進行貨幣計算。blog
這裏是對上面程序的直接轉換,使用BigDecimal類型代替double。 請注意,使用BigDecimal的String類型的構造方法,而不是其double類型構造方法。 這是必要的,以免在計算中引入不許確的值[Bloch05,Puzzle 2]:ip
public static void main(String[] args) { final BigDecimal TEN_CENTS = new BigDecimal(".10"); int itemsBought = 0; BigDecimal funds = new BigDecimal("1.00"); for (BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS)) { funds = funds.subtract(price); itemsBought++; } System.out.println(itemsBought + " items bought."); System.out.println("Money left over: $" + funds); }
若是你運行修改後的程序,你會發現能夠買到四塊糖果,剩下0.00美圓。 這是正確的答案。ci
可是,使用BigDecimal有兩個缺點:它沒有比使用基本算術類型方便,並且速度要慢得多。 若是你只解決一個簡單的問題,後一種缺點是可有可無的,但前者可能會讓你煩惱。get
除了使用BigDecimal之外,還可使用int或long類型,具體取決於所涉及的數量,並本身控制十進制小數點。 在這個例子中,最明顯的方法是用美分而不是美圓來計算。下面是採用這種方法的簡單轉換:
public static void main(String[] args) { int itemsBought = 0; int funds = 100; for (int price = 10; funds >= price; price += 10) { funds -= price; itemsBought++; } System.out.println(itemsBought + " items bought."); System.out.println("Cash left over: " + funds + " cents"); }
總之,對於任何須要精確答案的計算,不要使用float或double。若是但願系統控制十進制小數點,而且不介意不使用基本類型帶來的不便和成本,請使用BigDecimal。使用BigDecimal的另外一個好處是,它能夠徹底控制舍入,當執行須要舍入的操做時,能夠從八種舍入模式中進行選擇。若是你使用合法的舍入行爲執行業務計算,這將很是方便。若是性能是最重要的,那麼不介意本身控制十進制小數點,並且數量不是太大,可使用int或long。若是數量不超過9位小數,可使用int;若是不超過18位,可使用long。若是數量可能超過18位,則使用BigDecimal。