我总是被告知永远不要用双类型或浮点类型来表示货币,这一次我向你提出一个问题:为什么?

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


当前回答

我对其中一些回答感到困扰。我认为双数和浮点数在财务计算中占有一席之地。当然,在使用整数类或BigDecimal类时,在加减非分数货币金额时,不会损失精度。但是,当执行更复杂的操作时,无论您如何存储这些数字,您经常会得到小数点后几位或许多位的结果。问题在于你如何呈现结果。

如果你的结果是在四舍五入和四舍五入之间的边缘,最后一分真的很重要,你可能应该告诉观众答案几乎在中间——通过显示更多的小数点后数位。

双精度浮点数的问题是,当它们被用来组合大数和小数时。在java中,

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

结果

1.1875

其他回答

摘自Bloch, J., Effective Java,(第二版,第48项。第3版,项目60):

float和double类型是 尤其不适用于货币 因为这是不可能的 表示0.1(或任何其他。 10的负次方)作为浮点数或 完全的两倍。 例如,假设您有1.03美元 你花了42c。多少钱? 你走了? System.out.println(1.03 - .42); 输出0.6100000000000001。 解决这个问题的正确方法是 使用BigDecimal, int或long 用于货币计算。

虽然BigDecimal有一些警告(请参阅当前接受的答案)。

The result of floating point number is not exact, which makes them unsuitable for any financial calculation which requires exact result and not approximation. float and double are designed for engineering and scientific calculation and many times doesn’t produce exact result also result of floating point calculation may vary from JVM to JVM. Look at below example of BigDecimal and double primitive which is used to represent money value, its quite clear that floating point calculation may not be exact and one should use BigDecimal for financial calculations.

    // 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

如前所述,“把钱表示为双位数或浮点数,一开始可能看起来不错,因为软件会消除微小的错误,但当你对不精确的数字进行更多的加减乘除时,随着错误的增加,你会失去越来越多的精度。”这使得浮点数和双精度数不适用于处理货币,因为货币需要精确计算以10为底数的倍数。”

最后,Java有一个标准的方法来处理货币和金钱!

JSR 354:货币和货币API

JSR 354提供了一个API,用于表示、传输和执行Money和Currency的综合计算。你可以从以下连结下载:

JSR 354:货币和货币API下载

该规范包括以下内容:

用于处理例如货币数量和货币的API 支持可互换实现的api 用于创建实现类实例的工厂 用于计算、转换和格式化货币金额的功能 用于处理Money和Currencies的Java API,计划包含在Java 9中。 所有规范类和接口都位于javax.money中。*包。

JSR 354: Money and Currency API示例:

创建一个moneyaryamount并将其打印到控制台的示例如下:

MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

当使用参考实现API时,必要的代码要简单得多:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

该API还支持monetaryamount的计算:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));

CurrencyUnit和moneyaryamount

// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);

moneyaryamount有各种方法,允许访问指定的货币,数字金额,其精度和更多:

MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();

int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5

// NumberValue extends java.lang.Number.
// So we assign numberValue to a variable of type Number
Number number = numberValue;

monearyamount可以使用舍入运算符进行舍入:

CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35

当使用monearyamount的集合时,可以使用一些不错的实用程序方法进行过滤、排序和分组。

List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));

自定义monearyamount操作

// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
    BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
    BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
    return Money.of(tenPercent, amount.getCurrency());
};

MonetaryAmount dollars = Money.of(12.34567, "USD");

// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567

资源:

使用JSR 354在Java中处理金钱和货币

Java 9货币和货币API (JSR 354)

参见:JSR 354 -货币和货币

浮点数和双精度数是近似的。如果你创建了一个BigDecimal并将一个float传递给构造函数,你会看到float实际等于什么:

groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375

这可能不是您想要的表示1.01美元的方式。

问题是IEEE规范没有一种方法来精确地表示所有的分数,其中一些分数最终是重复的分数,所以你最终会得到近似错误。由于会计人员喜欢精确到每一分钱,如果客户支付账单,在付款处理后他们欠0.01,他们会被收取费用或无法关闭他们的帐户,那么最好使用精确的类型,如decimal(在c#中)或Java. math. bigdecimal。

这并不是说如果你四舍五入,误差就无法控制:请参阅Peter Lawrey的这篇文章。只是从一开始就不用四舍五入更容易。大多数处理资金的应用程序不需要大量的数学运算,操作包括添加东西或将金额分配到不同的存储空间。引入浮点数和舍入只会使事情复杂化。

我对其中一些回答感到困扰。我认为双数和浮点数在财务计算中占有一席之地。当然,在使用整数类或BigDecimal类时,在加减非分数货币金额时,不会损失精度。但是,当执行更复杂的操作时,无论您如何存储这些数字,您经常会得到小数点后几位或许多位的结果。问题在于你如何呈现结果。

如果你的结果是在四舍五入和四舍五入之间的边缘,最后一分真的很重要,你可能应该告诉观众答案几乎在中间——通过显示更多的小数点后数位。

双精度浮点数的问题是,当它们被用来组合大数和小数时。在java中,

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

结果

1.1875