Floating point 为什么不使用Double或Float来表示货币?

Floating point 为什么不使用Double或Float来表示货币?,floating-point,currency,Floating Point,Currency,我一直被告知永远不要用double或float类型来表示货币,这次我向你提出了一个问题:为什么 我确信有一个很好的理由,我只是不知道它是什么。因为浮点数和双精度不能准确地表示我们用于货币的基数10倍。这个问题不仅针对Java,还针对任何使用Base2浮点类型的编程语言 在10进制中,可以将10.25写成1025*10-2(10的幂的整数倍)。它们是不同的,但考虑它们的一个非常简单的方法是乘以二的幂。例如,你可以看到164*2-4(一个整数乘以二的幂),也就是10.25。这不是数字在内存中的表示方

我一直被告知永远不要用
double
float
类型来表示货币,这次我向你提出了一个问题:为什么


我确信有一个很好的理由,我只是不知道它是什么。

因为浮点数和双精度不能准确地表示我们用于货币的基数10倍。这个问题不仅针对Java,还针对任何使用Base2浮点类型的编程语言

在10进制中,可以将10.25写成1025*10-2(10的幂的整数倍)。它们是不同的,但考虑它们的一个非常简单的方法是乘以二的幂。例如,你可以看到164*2-4(一个整数乘以二的幂),也就是10.25。这不是数字在内存中的表示方式,但数学含义是相同的

即使以10为基数,这种表示法也不能准确地表示最简单的分数。例如,您不能表示1/3:十进制表示是重复的(0.3333…),因此没有可以乘以10的幂得到1/3的有限整数。你可以选择一个长的3的序列和一个小的指数,比如333*10-10,但这并不准确:如果你把它乘以3,你就得不到1

然而,为了计算货币,至少对于货币价值在美元数量级以内的国家来说,通常你所需要的只是能够存储10-2的倍数,因此1/3不能表示并不重要

浮动和双精度的问题在于,绝大多数类似于货币的数字没有一个整数乘以2的幂的精确表示。事实上,0到1之间的0.01的倍数(在处理货币时非常重要,因为它们是整数美分)可以精确表示为IEEE-754二进制浮点数,只有0、0.25、0.5、0.75和1。其他人都少了一点钱。与0.333333示例类似,如果将浮点值取为0.01,然后将其乘以10,则不会得到1。相反,您将得到0.099999786之类的值

将货币表示为
double
float
起初可能看起来不错,因为软件会将微小的错误舍入,但当您对不精确的数字执行更多的加法、减法、乘法和除法时,错误会复合,最终会得到明显不准确的值。这使得浮点数和双倍点数不足以处理货币,因为在处理货币时,要求基本10次方幂的倍数具有完美的精度


一种几乎适用于任何语言的解决方案是使用整数代替,并计算美分。例如,1025美元将是10.25美元。一些语言还内置了处理金钱的类型。除此之外,Java有类,C有类型。

因为浮点和双精度不能准确地表示我们用于货币的基数10倍。这个问题不仅针对Java,还针对任何使用Base2浮点类型的编程语言

在10进制中,可以将10.25写成1025*10-2(10的幂的整数倍)。它们是不同的,但考虑它们的一个非常简单的方法是乘以二的幂。例如,你可以看到164*2-4(一个整数乘以二的幂),也就是10.25。这不是数字在内存中的表示方式,但数学含义是相同的

即使以10为基数,这种表示法也不能准确地表示最简单的分数。例如,您不能表示1/3:十进制表示是重复的(0.3333…),因此没有可以乘以10的幂得到1/3的有限整数。你可以选择一个长的3的序列和一个小的指数,比如333*10-10,但这并不准确:如果你把它乘以3,你就得不到1

然而,为了计算货币,至少对于货币价值在美元数量级以内的国家来说,通常你所需要的只是能够存储10-2的倍数,因此1/3不能表示并不重要

浮动和双精度的问题在于,绝大多数类似于货币的数字没有一个整数乘以2的幂的精确表示。事实上,0到1之间的0.01的倍数(在处理货币时非常重要,因为它们是整数美分)可以精确表示为IEEE-754二进制浮点数,只有0、0.25、0.5、0.75和1。其他人都少了一点钱。与0.333333示例类似,如果将浮点值取为0.01,然后将其乘以10,则不会得到1。相反,您将得到0.099999786之类的值

将货币表示为
double
float
起初可能看起来不错,因为软件会将微小的错误舍入,但当您对不精确的数字执行更多的加法、减法、乘法和除法时,错误会复合,最终会得到明显不准确的值。这使得浮点数和双倍点数不足以处理货币,因为在处理货币时,要求基本10次方幂的倍数具有完美的精度


一种几乎适用于任何语言的解决方案是使用整数代替,并计算美分。例如,1025美元将是10.25美元。一些语言还内置了处理金钱的类型。除此之外,Java拥有类,C#拥有类型。

浮点和双精度是近似值。如果创建一个BigDecimal并将一个浮点值传递给构造函数,则可以看到该浮点值实际上等于:

groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375
这可能不是您想要表示1.01美元的方式

问题是IEEE规范没有一种精确表示所有分数的方法,其中一些分数最终会成为重复分数,因此最终会出现近似错误。因为会计师喜欢把事情说得一清二楚
System.out.println(1.03 - .42);
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;
    }
}
System.out.println(1000000.0f + 1.2f - 1000000.0f);
1.1875
    // 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
    double total = 0.0;

    // do 10 adds of 10 cents
    for (int i = 0; i < 10; i++) {
        total += 0.1;  // adds 10 cents
    }

    Log.d("round problems?", "current total: " + total);

    // looks like total equals to 1.0, don't?

    // now, do reverse
    for (int i = 0; i < 10; i++) {
        total -= 0.1;  // removes 10 cents
    }

    // looks like total equals to 0.0, don't?
    Log.d("round problems?", "current total: " + total);
    if (total == 0.0) {
        Log.d("round problems?", "is total equal to ZERO? YES, of course!!");
    } else {
        Log.d("round problems?", "is total equal to ZERO? No...");
        // so be careful comparing equality in this cases!!!
    }
 round problems?: current total: 0.9999999999999999
 round problems?: current total: 2.7755575615628914E-17
 round problems?: is total equal to ZERO? No... 
<dependency>
  <groupId>org.joda</groupId>
  <artifactId>joda-money</artifactId>
  <version>1.0.1</version>
</dependency>
  // create a monetary value
  Money money = Money.parse("USD 23.87");
  
  // add another amount with safe double conversion
  CurrencyUnit usd = CurrencyUnit.of("USD");
  money = money.plus(Money.of(usd, 12.43d));
  
  // subtracts an amount in dollars
  money = money.minusMajor(2);
  
  // multiplies by 3.5 with rounding
  money = money.multipliedBy(3.5d, RoundingMode.DOWN);
  
  // compare two amounts
  boolean bigAmount = money.isGreaterThan(dailyWage);
  
  // convert to GBP using a supplied rate
  BigDecimal conversionRate = ...;  // obtained from code outside Joda-Money
  Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);
  
  // use a BigMoney for more complex calculations where scale matters
  BigMoney moneyCalc = money.toBigMoney();