在Java中用于赚钱的数据类型是什么?[关闭]


183

在Java中,您应该使用哪种数据类型来赚钱?


2
这取决于您要执行的操作。请提供更多信息。
eversor

@eversor您能给我说明不同操作应使用哪种数据类型吗?
Questborn

1
我正在做计算,这需要我准确地代表美分。
Questborn

您是否可以预测应用程序需要处理的最大金额?而且,根据您的计算,它们是简单的(费用等)还是更复杂的财务操作?
eversor

Answers:


133

Java具有Currency代表ISO 4217货币代码的类。 BigDecimal是表示货币十进制值的最佳类型。

Joda Money提供了一个代表金钱的图书馆。


5
为什么我们不能使用float或double?
Erran Morad 2014年

20
@Borat Sagdiyev 这就是原因。另外,您可以参考this
Buhake Sindi 2014年

2
@Borat:如果您知道自己在做什么,可以参阅Peter Lawrey的这篇文章。但是似乎所有的舍入操作和使用BigDecimals一样至少有很大的麻烦。
内森·休斯

35
“如果每次看到有人使用FLOAT来存储货币时,我都会得到一角钱,我将有999.997634美元。”-Bill Karwin
Collin Krawll

36

您可以使用Money and Currency API(JSR 354)。只要向项目中添加适当的依赖项,就可以使用此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") );
    }
}

序列化并保存到db中呢?通过有线发送应使用哪种格式?
帕维尔Szczur

1
我相信Oracle致力于在Java 9中包括Java Money。但是很好的答案。我们仍然可以在Maven中使用它
borjab '16

3
您是否有Oracle的消息来源决定不将Java Money包含在Java 9中?
阿卜杜勒

26

表示最小值的整数类型。换句话说,您的程序应该以美分而非美元/欧元来考虑。

这不应阻止您让gui将其转换回美元/欧元。


记住,资金量可以溢出int的长度
eversor

5
@eversor需要超过2000万美元,如果长时间运行,大多数应用程序将不需要太多,因为即使我们的政府部门也处理不了足够的资金以至于无法满足
棘手的怪胎

4
@ratchetfreak可能最好使用较长的时间。
trognanders,2014年

4
许多银行每天要处理的金额远远超过2000万美元。这甚至没有考虑到日元对美元汇率高的货币。整数类型可能最好避免圆整问题,尽管它们会使利息和汇率计算变得混乱。但是,根据应用程序,您可能需要64位整数类型。
Alchymist 2015年

实际上,理想情况下是微美元,例如,如果您进行了例如$ 10/3的操作,则舍入误差(3333.3 => 3333.0)不会对最终值产生太大的影响(在这种情况下,它根本不会影响实际值,尽管假设它永远不会危险)。如果您要在用户看到结果之前连续进行大量计算,这一点尤其重要,因为四舍五入的误差会加重。
克里斯·布朗


11

JSR 354:货币和货币API

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

JSR 354:货币和货币API下载

该规范包括以下内容:

  1. 用于处理货币金额和货币的API
  2. 支持互换实现的API
  3. 用于创建实现类实例的工厂
  4. 金额计算,转换和格式化的功能
  5. 计划将Java 9中包含的用于处理Money and Currencies的Java API。
  6. 所有规范类和接口都位于javax.money。*包中。

JSR 354的示例示例:货币和货币API:

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

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"));

货币单位和货币金额

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

可以使用舍入运算符舍入MonetaryAmount:

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的集合时,可以使用一些不错的实用程序方法进行过滤,排序和分组。

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 Money and Currency API(JSR 354)

另请参见:JSR 354-货币和货币


所有这些都很好,但是正如Federico上面所建议的那样,它看起来比BigDecimal慢:-))那只是个恶作剧,但我将在一年后对其进行测试...
肯赛

6

您应该使用BigDecimal 来表示货币值。它允许您使用各种舍入模式,在金融应用中,舍入模式通常是一个硬要求,甚至可能是法律规定的。



6

我已经做了一个微基准测试(JMH),以比较Moneta(Java货币JSR 354实现)与BigDecimal的性能。

令人惊讶的是,BigDecimal的性能似乎比moneta更好。我使用了以下moneta配置:

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

如果我缺少某些东西,请随时纠正我


有趣的是,我将运行与JDK9最新的东西,同样的测试
剑圣

4

对于简单情况(一种货币),就足够了Integer/ Long。将钱保持在美分(...)或百分之一/千分之一(固定分频器需要的任何精度)


3

BigDecimal是用于货币的最佳数据类型。

有很多用于货币的容器,但是它们都使用BigDecimal作为基础数据类型。使用BigDecimal不会出错,可能使用BigDecimal.ROUND_HALF_EVEN舍入。


2

我喜欢使用Tiny Types,它可以包装double,BigDecimal或int,如先前的答案所示。(除非精度问题出现,否则我将使用double)。

微型打字机为您提供打字安全性,因此您不会将双钱与其他双打混淆。


6
虽然我也喜欢小类型,但您永远不要使用双精度来存储货币值。
orien 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.