为何不使用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之类的结果。 所以,您在每一个步骤中都会有一些小错误。

该错误将累加,最终可能变得不容易被忽略。 这称为错误传播

相关文章
相关标签/搜索