爲何不使用Double或Float表明貨幣?

一直有人告訴我,永遠不要double float型或float型表明錢,這一次我向你提出一個問題:爲何? java

我敢確定有一個很好的理由,我根本不知道這是什麼。 ide


#1樓

這不是精度問題,也不是精度問題。 這是要知足使用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


#2樓

確實,浮點類型只能表示近似十進制數據,但若是在顯示數字以前將數字四捨五入到必要的精度,則也能夠獲得正確的結果。 一般。 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;
    }
}

#3樓

我對其中的一些迴應感到困擾。 我認爲雙打和浮動交易在財務計算中佔有一席之地。 固然,當使用整數類或BigDecimal類進行加減時,不會減小精度。 可是,當執行更復雜的操做時,不管以何種方式存儲數字,結果一般會排到幾個或多個小數位。 問題是您如何呈現結果。

若是您的結果是在四捨五入和四捨五入之間的邊界上,而最後一分錢確實很重要,那麼您應該告訴觀衆答案几乎在中間-經過顯示更多的小數位。

雙精度數(尤爲是浮點數)的問題在於將它們用於組合大數和小數時。 在Java中,

System.out.println(1000000.0f + 1.2f - 1000000.0f);

結果是

1.1875

#4樓

浮點數的結果不精確,這使其不適用於須要精確結果而不是近似值的任何財務計算。 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

#5樓

若是您的計算涉及多個步驟,那麼任意精度的算術都不會100%覆蓋您。

使用結果的完美表示的惟一可靠方法(使用自定義的Fraction數據類型,該數據類型將對最後一步進行除法運算)而且僅在最後一步中轉換爲十進制表示法。

任意精度將無濟於事,由於總會有小數位那麼多的數字,或者諸如0.6666666之類的結果。 所以,您在每一個步驟中都會有一些小錯誤。

該錯誤將累加,最終可能變得不容易被忽略。 這稱爲錯誤傳播

相關文章
相關標籤/搜索