一直有人告訴我,永遠不要用double
float
型或float
型表明錢,這一次我向你提出一個問題:爲何? java
我敢確定有一個很好的理由,我根本不知道這是什麼。 ide
這不是精度問題,也不是精度問題。 這是要知足使用10而不是2進行計算的人們的指望的問題。例如,使用double進行財務計算不會產生數學意義上「錯誤」的答案,但能夠得出答案。而不是財務上的預期。 測試
即便您在輸出前的最後一分鐘對結果進行四捨五入,您仍然偶爾會使用不符合預期的雙精度來得到結果。 spa
使用計算器或手動計算結果,則精確地爲1.40 * 165 = 231。 可是,在內部使用雙打,在個人編譯器/操做系統環境中,它存儲爲接近230.99999的二進制數字...所以,若是截斷該數字,則會獲得230而不是231。您可能會認爲舍入而不是截斷會給出了231的指望結果。是的,可是舍入老是涉及截斷。 不管您使用哪一種舍入技術,仍然存在像這樣的邊界條件,當您指望將其舍入時,該邊界條件將舍入。 它們很是稀有,常常經過偶然的測試或觀察將不會被發現。 您可能必須編寫一些代碼來搜索示例,這些示例說明結果與預期不符。 操作系統
假設您想四捨五入到最接近的美分。 這樣就獲得了最終結果,乘以100,再加上0.5,截斷,而後將結果除以100,即可以獲得幾分錢。 若是您存儲的內部號碼是3.46499999 ....而不是3.465,則將數字四捨五入時將獲得3.46而不是3.47。 可是,以10爲基數的計算可能已經代表,答案應該剛好是3.465,顯然應該向上舍入爲3.47,而不是向下爲3.46。 當您使用倍數進行財務計算時,這類事情偶爾會在現實生活中發生。 它不多見,所以一般不會引發人們的注意,可是它確實會發生。 設計
若是您使用10爲基數進行內部計算而不是使用雙精度數,那麼假設代碼中沒有其餘錯誤,答案老是徹底是人類指望的結果。 code
確實,浮點類型只能表示近似十進制數據,但若是在顯示數字以前將數字四捨五入到必要的精度,則也能夠獲得正確的結果。 一般。 orm
一般由於double類型的精度小於16個數字。 若是您須要更高的精度,則不適合使用此類型。 近似值也會累積。 ci
必須說,即便您使用定點算術,您仍然必須對數字進行四捨五入,這是否不是由於若是您得到週期十進制數,則BigInteger和BigDecimal會給出錯誤。 所以,這裏也有一個近似值。 get
例如,過去用於財務計算的COBOL的最大精度爲18個數字。 所以一般會有一個隱式的舍入。
最後,我認爲雙精度不適合其16位精度,這多是不夠的,不是由於它是近似值。
考慮後續程序的如下輸出。 它顯示在四捨五入後,得出與BigDecimal相同的結果,精度爲16。
Precision 14 ------------------------------------------------------ BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result. DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5 BigDecimal : 56789.012345 / 1111111111 = 0.000051110111115611 Double : 56789.012345 / 1111111111 = 0.000051110111115611 Precision 15 ------------------------------------------------------ BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result. DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5 BigDecimal : 56789.012345 / 1111111111 = 0.0000511101111156110 Double : 56789.012345 / 1111111111 = 0.0000511101111156110 Precision 16 ------------------------------------------------------ BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result. DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5 BigDecimal : 56789.012345 / 1111111111 = 0.00005111011111561101 Double : 56789.012345 / 1111111111 = 0.00005111011111561101 Precision 17 ------------------------------------------------------ BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result. DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5 BigDecimal : 56789.012345 / 1111111111 = 0.000051110111115611011 Double : 56789.012345 / 1111111111 = 0.000051110111115611013 Precision 18 ------------------------------------------------------ BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result. DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5 BigDecimal : 56789.012345 / 1111111111 = 0.0000511101111156110111 Double : 56789.012345 / 1111111111 = 0.0000511101111156110125 Precision 19 ------------------------------------------------------ BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result. DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5 BigDecimal : 56789.012345 / 1111111111 = 0.00005111011111561101111 Double : 56789.012345 / 1111111111 = 0.00005111011111561101252
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.MathContext; public class Exercise { public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { String amount = "56789.012345"; String quantity = "1111111111"; int [] precisions = new int [] {14, 15, 16, 17, 18, 19}; for (int i = 0; i < precisions.length; i++) { int precision = precisions[i]; System.out.println(String.format("Precision %d", precision)); System.out.println("------------------------------------------------------"); execute("BigDecimalNoRound", amount, quantity, precision); execute("DoubleNoRound", amount, quantity, precision); execute("BigDecimal", amount, quantity, precision); execute("Double", amount, quantity, precision); System.out.println(); } } private static void execute(String test, String amount, String quantity, int precision) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { Method impl = Exercise.class.getMethod("divideUsing" + test, String.class, String.class, int.class); String price; try { price = (String) impl.invoke(null, amount, quantity, precision); } catch (InvocationTargetException e) { price = e.getTargetException().getMessage(); } System.out.println(String.format("%-30s: %s / %s = %s", test, amount, quantity, price)); } public static String divideUsingDoubleNoRound(String amount, String quantity, int precision) { // acceptance double amount0 = Double.parseDouble(amount); double quantity0 = Double.parseDouble(quantity); //calculation double price0 = amount0 / quantity0; // presentation String price = Double.toString(price0); return price; } public static String divideUsingDouble(String amount, String quantity, int precision) { // acceptance double amount0 = Double.parseDouble(amount); double quantity0 = Double.parseDouble(quantity); //calculation double price0 = amount0 / quantity0; // presentation MathContext precision0 = new MathContext(precision); String price = new BigDecimal(price0, precision0) .toString(); return price; } public static String divideUsingBigDecimal(String amount, String quantity, int precision) { // acceptance BigDecimal amount0 = new BigDecimal(amount); BigDecimal quantity0 = new BigDecimal(quantity); MathContext precision0 = new MathContext(precision); //calculation BigDecimal price0 = amount0.divide(quantity0, precision0); // presentation String price = price0.toString(); return price; } public static String divideUsingBigDecimalNoRound(String amount, String quantity, int precision) { // acceptance BigDecimal amount0 = new BigDecimal(amount); BigDecimal quantity0 = new BigDecimal(quantity); //calculation BigDecimal price0 = amount0.divide(quantity0); // presentation String price = price0.toString(); return price; } }
我對其中的一些迴應感到困擾。 我認爲雙打和浮動交易在財務計算中佔有一席之地。 固然,當使用整數類或BigDecimal類進行加減時,不會減小精度。 可是,當執行更復雜的操做時,不管以何種方式存儲數字,結果一般會排到幾個或多個小數位。 問題是您如何呈現結果。
若是您的結果是在四捨五入和四捨五入之間的邊界上,而最後一分錢確實很重要,那麼您應該告訴觀衆答案几乎在中間-經過顯示更多的小數位。
雙精度數(尤爲是浮點數)的問題在於將它們用於組合大數和小數時。 在Java中,
System.out.println(1000000.0f + 1.2f - 1000000.0f);
結果是
1.1875
浮點數的結果不精確,這使其不適用於須要精確結果而不是近似值的任何財務計算。 float和double是爲工程和科學計算而設計的,不少時候也沒法得出準確的結果,並且浮點計算的結果可能因JVM而異。 請看下面的示例BigDecimal和用於表示貨幣價值的double原語,它很清楚地代表浮點數計算可能並不精確,所以應該使用BigDecimal進行財務計算。
// floating point calculation final double amount1 = 2.0; final double amount2 = 1.1; System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2)); // Use BigDecimal for financial calculation final BigDecimal amount3 = new BigDecimal("2.0"); final BigDecimal amount4 = new BigDecimal("1.1"); System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));
輸出:
difference between 2.0 and 1.1 using double is: 0.8999999999999999 difference between 2.0 and 1.1 using BigDecimal is: 0.9
若是您的計算涉及多個步驟,那麼任意精度的算術都不會100%覆蓋您。
使用結果的完美表示的惟一可靠方法(使用自定義的Fraction數據類型,該數據類型將對最後一步進行除法運算)而且僅在最後一步中轉換爲十進制表示法。
任意精度將無濟於事,由於總會有小數位那麼多的數字,或者諸如0.6666666之類的結果。 所以,您在每一個步驟中都會有一些小錯誤。
該錯誤將累加,最終可能變得不容易被忽略。 這稱爲錯誤傳播 。