web-dev-qa-db-fra.com

Quel est le meilleur type de données à utiliser pour de l'argent dans Java app?

Quel est le meilleur type de données à utiliser pour de l'argent dans l'application Java?

158
questborn

Java a la classe Currency qui représente les codes de devise ISO 4217. BigDecimal est le meilleur type pour représenter les valeurs décimales en devise.

Joda Money a fourni une bibliothèque pour représenter l'argent.

118
Buhake Sindi

Vous pouvez utiliser API Money and Currency (JSR 354). Vous pouvez utiliser cette API dans, à condition d'ajouter les dépendances appropriées à votre projet.

Pour Java 8, ajoutez l'implémentation de référence suivante en tant que dépendance de votre pom.xml:

<dependency>
    <groupId>org.javamoney</groupId>
    <artifactId>moneta</artifactId>
    <version>1.0</version>
</dependency>

Cette dépendance ajoutera transitoirement javax.money:money-api en tant que dépendance.

Vous pouvez ensuite utiliser l'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") );
    }
}
33
Abdull

Un type intégral représentant la plus petite valeur possible. En d’autres termes, votre programme doit penser en centimes pas en dollars/euros.

Cela ne devrait pas vous empêcher d’avoir l’interface graphique à traduire en dollars/euros.

22
ratchet freak

BigDecimal peut être utilisé, une bonne explication de pourquoi ne pas utiliser Float ou Double peut être vu ici: Pourquoi ne pas utiliser Double ou Float pour représenter la devise?

11
Phil Parsons

JSR 354: API Money and Currency

JSR 354 fournit une API permettant de représenter, de transporter et d'effectuer des calculs complets avec Money et Currency. Vous pouvez le télécharger à partir de ce lien:

JSR 354: Téléchargement de l’API monétaire et monétaire

La spécification comprend les éléments suivants:

  1. Une API pour gérer e. g. montants et devises
  2. API pour prendre en charge des implémentations interchangeables
  3. Fabriques pour créer des instances des classes d'implémentation
  4. Fonctionnalité pour les calculs, la conversion et la mise en forme des montants monétaires
  5. API Java permettant de travailler avec Money and Currency, qui devrait être incluse dans Java 9.
  6. Toutes les classes et interfaces de spécification sont situées dans le package javax.money. *.

Exemples de réalisation de l'API JSR 354: Monnaie et monnaie:

Voici un exemple de création d’un MonetaryAmount et de son impression sur la console:

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

Lorsque vous utilisez l'API d'implémentation de référence, le code nécessaire est beaucoup plus simple:

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

L'API prend également en charge les calculs avec MonetaryAmounts:

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

CurrencyUnit et MonetaryAmount

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

MonetaryAmount dispose de diverses méthodes permettant d’accéder à la devise affectée, au montant numérique, à sa précision, etc.:

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 peut être arrondi à l'aide d'un opérateur d'arrondi:

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

Lorsque vous travaillez avec des collections de MonetaryAmounts, certaines méthodes utilitaires de Nice pour le filtrage, le tri et le regroupement sont disponibles.

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

Opérations personnalisées 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

Ressources:

Traitement de l'argent et des devises dans Java avec JSR 354

En regardant dans la Java 9 API Money and Currency (JSR 354)

Voir aussi: JSR 354 - Monnaie et monnaie

9
Virtual

J'ai effectué un microbenchmark (JMH) pour comparer Moneta (implémentation de la devise Java JSR 354) à BigDecimal en termes de performances.

Étonnamment, les performances BigDecimal semblent être meilleures que celles de moneta. J'ai utilisé la configuration suivante:

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

Résultant en

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

S'il vous plaît, n'hésitez pas à me corriger si quelque chose me manque

Je voudrais utiliser Joda Money

Il est toujours à la version 0.6 mais semble très prometteur

6
Liviu T.

Vous devez utiliser BigDecimal pour représenter les valeurs monétaires. Il vous permet d’utiliser une variété de modes d’arrondissement , ainsi que dans les applications financières, le mode d'arrondi est souvent une exigence difficile qui peut même être imposée par la loi.

5
Sandeep Pathak

Pour un cas simple (une devise), cela suffit Integer/Long. Gardez votre argent en cents (...) ou en centièmes/millièmes de cent (toute précision dont vous avez besoin avec un diviseur fixe)

4
GKislin

BigDecimal est le meilleur type de données à utiliser pour la devise.

Il existe un grand nombre de conteneurs pour la devise, mais ils utilisent tous BigDecimal comme type de données sous-jacent. BigDecimal ne vous tracas pas, utilisez probablement l'arrondi BigDecimal.ROUND_HALF_EVEN.

3
Anthony Blake

J'aime utiliser Tiny Types qui encapsulerait soit un double, un BigDecimal ou un int comme les réponses précédentes l'ont suggéré. (J'utiliserais un double sauf si des problèmes de précision surgissent).

Un type minuscule vous donne la sécurité de type afin que vous ne confondiez pas un double argent avec d'autres doubles.

2
Garrett Smith