很久以前,我寫了一篇關于用雙倍賺錢的文章。 但是,當解決方案相當簡單時,仍然是許多開發人員普遍擔心的問題。
用雙倍賺錢的問題
double有兩種類型的錯誤。 它存在表示錯誤。 即,它不能完全代表所有可能的十進制值。 即使0.1也不完全是這個值。 根據計算也有舍入誤差。 即,當您執行計算時,誤差會增加。
double[] ds = {0.1,0.2,-0.3,0.1 + 0.2 - 0.3};for (double d : ds) {System.out.println(d + " => " + new BigDecimal(d));
}
版畫
0.1 => 0.1000000000000000055511151231257827021181583404541015625
0.2 => 0.200000000000000011102230246251565404236316680908203125
-0.3 => -0.299999999999999988897769753748434595763683319091796875
5.551115123125783E-17 => 5.5511151231257827021181583404541015625E-17
您可以看到0.1和0.2的表示形式略高于這些值,-0.3的表示形式也略高。 當您打印它們時,您得到的更好的是0.1,而不是代表的實際值0.1000000000000000055511151231257827021181583404541015625
但是,將這些值加在一起時,會得到一個略高于0的值。
要記住的重要一點是這些錯誤不是隨機錯誤。 它們是可管理的且受限制的。
修正舍入誤差
像許多數據類型(例如日期)一樣,您具有值的內部表示形式以及如何將其表示為字符串。
對于double來說是這樣。 您需要控制如何將值表示為字符串。 由于Java對表示錯誤進行少量舍入并不明顯,所以這可能會令人驚訝,但是,一旦對操作也產生舍入錯誤,就會有些震驚。
一個常見的反應是假設,您對此無能為力,該錯誤是無法控制,不可知且危險的。 放棄雙倍并使用BigDecimal
但是,該錯誤在IEE-754標準中受到限制,并且累積緩慢。
舍入結果
就像需要使用TimeZone和Local作為日期一樣,您需要在轉換為String之前確定結果的精度。
要解決此問題,您需要提供適當的舍入。 有了錢,這很容易,因為您知道合適的小數位數,除非您有70萬億美元,否則舍入誤差不會大到無法校正的程度。
// uses round half up, or bankers' roundingpublic static double roundToTwoPlaces(double d) {return Math.round(d * 100) / 100.0;
}// ORpublic static double roundToTwoPlaces(double d) {return ((long) (d < 0 ? d * 100 - 0.5 : d * 100 + 0.5)) / 100.0;
}
如果將其添加到結果中,仍然會出現一個很小的表示錯誤,但是Double.toString(d)無法對其進行校正,不足以表示該錯誤。
double[] ds = {0.1,0.2,-0.3,0.1 + 0.2 - 0.3};for (double d : ds) {System.out.println(d + " to two places " + roundToTwoPlaces(d) + " => " + new BigDecimal(roundToTwoPlaces(d)));
}
版畫
0.1 to two places 0.1 => 0.1000000000000000055511151231257827021181583404541015625
0.2 to two places 0.2 => 0.200000000000000011102230246251565404236316680908203125
-0.3 to two places -0.3 => -0.299999999999999988897769753748434595763683319091796875
5.551115123125783E-17 to two places 0.0 => 0
結論
如果您有一個項目標準說應該使用BigDecimal或double,則應遵循該標準。 但是,沒有充分的技術理由擔心會花雙倍的錢。
參考:在Vanilla Java上 ,我們的JCG合作伙伴 Peter Lawrey 再次增加了您的資金 。
- Java中的低GC:使用原語而不是包裝器
- Java Lambda語法替代
- JVM如何處理鎖
- Erlang與Java內存架構
- Java Fork / Join進行并行編程
- Java最佳實踐系列
- 如何在Java中獲得類似于C的性能
翻譯自: https://www.javacodegeeks.com/2011/08/double-your-money-again.html