什么是最好的数据types用于在Java应用程序的钱?
Java应用程序中用于金钱的最佳数据types是什么?
Java具有代表ISO 4217货币代码的Currency
类。 BigDecimal
是表示货币小数值的最佳types。
Joda Money提供了一个代表金钱的图书馆。
您可以使用货币和货币API(JSR 354) 。 这个API预计将成为Java 9的一部分。如果您向项目添加适当的依赖关系,则可以在Java 7和Java 8中使用此API。
对于Java 8,将以下参考实现作为依赖项添加到您的pom.xml
:
<dependency> <groupId>org.javamoney</groupId> <artifactId>moneta</artifactId> <version>1.0</version> </dependency>
这种依赖性会将javax.money:money-api
作为依赖项传递添加。
您可以使用API:
package com.example.money; import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; import java.util.Locale; import javax.money.Monetary; import javax.money.MonetaryAmount; import javax.money.MonetaryRounding; import javax.money.format.MonetaryAmountFormat; import javax.money.format.MonetaryFormats; import org.junit.Test; public class MoneyTest { @Test public void testMoneyApi() { MonetaryAmount eurAmount1 = Monetary.getDefaultAmountFactory().setNumber(1.1111).setCurrency("EUR").create(); MonetaryAmount eurAmount2 = Monetary.getDefaultAmountFactory().setNumber(1.1141).setCurrency("EUR").create(); MonetaryAmount eurAmount3 = eurAmount1.add(eurAmount2); assertThat(eurAmount3.toString(), is("EUR 2.2252")); MonetaryRounding defaultRounding = Monetary.getDefaultRounding(); MonetaryAmount eurAmount4 = eurAmount3.with(defaultRounding); assertThat(eurAmount4.toString(), is("EUR 2.23")); MonetaryAmountFormat germanFormat = MonetaryFormats.getAmountFormat(Locale.GERMAN); assertThat(germanFormat.format(eurAmount4), is("EUR 2,23") ); } }
表示可能的最小值的整数types。 换句话说,你的程序应该以美分来计算,而不是以美元/欧元计算。
这不应该阻止你把gui翻译成美元/欧元。
可以使用BigDecimal ,为什么不使用Float或Double可以在这里看到很好的解释: 为什么不使用Double或者Float来表示货币?
JSR 354:货币和货币API
JSR 354提供了一个用货币和货币来表示,运输和执行综合计算的API。 你可以从这个链接下载:
JSR 354:货币和货币API下载
规范包含以下内容:
- 用于处理货币金额和货币的API
- 支持可互换实现的API
- 工厂创build实现类的实例
- 货币金额的计算,转换和格式化的function
- 用于处理货币和货币的Java API,计划包含在Java 9中。
- 所有的规范类和接口都位于javax.money。*包中。
JSR 354的示例示例:货币和货币API:
创buildMonetaryAmount并将其打印到控制台的示例如下所示:
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还支持MonetaryAmounts的计算:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR"); MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));
CurrencyUnit和MonetaryAmount
// getting CurrencyUnits by locale CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN); CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MonetaryAmount有各种方法允许访问指定的货币,数量,其精度和更多:
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;
货币余额可以使用舍入运算符四舍五入:
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
在处理MonetaryAmounts集合时,可以使用一些很好的实用方法进行过滤,sorting和分组。
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"));
自定义MonetaryAmount操作
// 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 – 货币和货币
我会用Joda Money
它仍然在0.6版本,但看起来很有希望
您应该使用BigDecimal来表示货币价值。它允许您使用多种舍入模式 ,而在财务应用中,舍入模式往往是一项甚至可能受法律约束的硬性要求。
BigDecimal是用于货币的最佳数据types。
货币有很多容器,但都使用BigDecimal作为基础数据types。 BigDecimal不会出错,可能使用BigDecimal.ROUND_HALF_EVEN四舍五入。
我喜欢使用Tinytypes来包装double,BigDecimal或int,如前面的答案所build议的。 (我会使用双精度,除非出现精度问题)。
一个微小的types给你types安全,所以你不要把双重金钱与其他双打混淆。
我已经做了一个microbenchmark(JMH)来比较Moneta(Java货币JSR 354实现)与BigDecimal的性能。
令人惊讶的是,BigDecimal的performance似乎比moneta好。 我已经使用下面的monetaconfiguration:
org.javamoney.moneta.Money.defaults.precision = 19 org.javamoney.moneta.Money.defaults.roundingMode = HALF_UP
package com.despegar.bookedia.money; import org.javamoney.moneta.FastMoney; import org.javamoney.moneta.Money; import org.openjdk.jmh.annotations.*; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.concurrent.TimeUnit; @Measurement(batchSize = 5000, iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS) @Warmup(iterations = 2) @Threads(value = 1) @Fork(value = 1) @State(Scope.Benchmark) @BenchmarkMode(Mode.Throughput) public class BigDecimalBenchmark { private static final Money MONEY_BASE = Money.of(1234567.3444, "EUR"); private static final Money MONEY_SUBSTRACT = Money.of(232323, "EUR"); private static final FastMoney FAST_MONEY_SUBSTRACT = FastMoney.of(232323, "EUR"); private static final FastMoney FAST_MONEY_BASE = FastMoney.of(1234567.3444, "EUR"); MathContext mc = new MathContext(10, RoundingMode.HALF_UP); @Benchmark public void bigdecimal_string() { new BigDecimal("1234567.3444").subtract(new BigDecimal("232323")).multiply(new BigDecimal("3.4"), mc).divide(new BigDecimal("5.456"), mc); } @Benchmark public void bigdecimal_valueOf() { BigDecimal.valueOf(12345673444L, 4).subtract(BigDecimal.valueOf(232323L)).multiply(BigDecimal.valueOf(34, 1), mc).divide(BigDecimal.valueOf(5456, 3), mc); } @Benchmark public void fastmoney() { FastMoney.of(1234567.3444, "EUR").subtract(FastMoney.of(232323, "EUR")).multiply(3.4).divide(5.456); } @Benchmark public void money() { Money.of(1234567.3444, "EUR").subtract(Money.of(232323, "EUR")).multiply(3.4).divide(5.456); } @Benchmark public void money_static(){ MONEY_BASE.subtract(MONEY_SUBSTRACT).multiply(3.4).divide(5.456); } @Benchmark public void fastmoney_static() { FAST_MONEY_BASE.subtract(FAST_MONEY_SUBSTRACT).multiply(3.4).divide(5.456); } }
导致
Benchmark Mode Cnt Score Error Units BigDecimalBenchmark.bigdecimal_string thrpt 10 479.465 ± 26.821 ops/s BigDecimalBenchmark.bigdecimal_valueOf thrpt 10 1066.754 ± 40.997 ops/s BigDecimalBenchmark.fastmoney thrpt 10 83.917 ± 4.612 ops/s BigDecimalBenchmark.fastmoney_static thrpt 10 504.676 ± 21.642 ops/s BigDecimalBenchmark.money thrpt 10 59.897 ± 3.061 ops/s BigDecimalBenchmark.money_static thrpt 10 184.767 ± 7.017 ops/s
请随时纠正我,如果我失去了一些东西
对于简单的情况(一种货币),它是足够的Integer
/ Long
。 保持钱分(…)或百分之一千分之一(任何精度,你需要与固定分频器)