Какой тип данных использовать для денег в Java?

Какой тип данных вы должны использовать для денег в Java?

Ответ 1

Java имеет Currency класс, который представляет коды кодов ISO 4217. BigDecimal - лучший тип для представления десятичных значений валюты.

Joda Money предоставил библиотеку для представления денег.

Ответ 2

Вы можете использовать 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") );
    }
}

Ответ 3

Интегральный тип, представляющий наименьшее возможное значение. Другими словами, ваша программа должна думать в центах не в долларах/евро.

Это не должно помешать вам перевести его обратно в доллары/евро.

Ответ 5

JSR 354: API денег и валют

JSR 354 предоставляет API для представления, транспортировки и выполнения комплексных расчетов с помощью Money and Currency. Вы можете скачать его по этой ссылке:

JSR 354: Загрузка денег и валюты API

Спецификация состоит из следующих вещей:

  • API для обработки e. г. денежные суммы и валюты.
  • API для поддержки взаимозаменяемых реализаций
  • Заводы для создания экземпляров классов реализации
  • Функциональность для расчетов, преобразования и форматирования денежных сумм
  • API Java для работы с деньгами и валютами, который планируется включить в Java 9.
  • Все классы спецификации и интерфейсы расположены в пакете 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"));

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;

MonetaryAmounts можно округлить с помощью оператора округления:

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

Ресурсы:

Обработка денег и валют на Java с помощью JSR 354

Вглядываясь в Java 9 Money and Currency API (JSR 354)

Смотрите также: JSR 354 - Валюта и деньги

Ответ 6

Я бы использовал Joda Money

Он все еще находится в версии 0.6, но выглядит очень перспективным

Ответ 7

Я сделал микропредметку (JMH) для сравнения Moneta (java currency 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

Пожалуйста, не стесняйтесь исправлять меня, если я чего-то не хватает

Ответ 8

Для представления денежных значений вы должны использовать BigDecimal. Он позволяет использовать различные режимы округления и в финансовые приложения, режим округления часто является жестким требованием которые могут быть санкционированы законом.

Ответ 9

Для простого случая (одна валюта) достаточно Integer/Long. Держите деньги в центах (...) или сотых/тысячных центов (любая точность, которая вам нужна с фиксированным делителем)

Ответ 10

BigDecimal - лучший тип данных для использования в валюте.

Существует множество контейнеров для валюты, но все они используют BigDecimal в качестве базового типа данных. Вы не ошибетесь в BigDecimal, возможно, используя округление BigDecimal.ROUND_HALF_EVEN.

Ответ 11

Мне нравится использовать Tiny Types, который бы обернул либо двойной, BigDecimal, либо int, как предполагали предыдущие ответы. (Я бы использовал двойной, если не возникли проблемы с точностью).

A Tiny Type дает вам безопасность типа, поэтому вы не путаете двойные деньги с другими двойниками.