Quel type de données utiliser pour de l'argent en Java? [fermé]

183

Quel type de données devriez-vous utiliser pour de l'argent en Java?

questborn
la source
2
Cela dépend des opérations que vous allez faire. Veuillez offrir plus d'informations.
eversor
@eversor Pouvez-vous me donner une description du type de données à utiliser pour différentes opérations?
questborn
1
Je fais des calculs qui m'obligent à représenter avec précision les cents.
questborn
Êtes-vous en mesure de prédire la plus grosse somme d'argent que votre application devra gérer? Et, vos calculs, vont-ils être des opérations financières simples (aditions etc.) ou plus complexes?
Eversor

Réponses:

133

Java a une Currencyclasse qui représente les codes de devise ISO 4217. BigDecimalest le meilleur type pour représenter les valeurs décimales des devises.

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

Buhake Sindi
la source
5
Pourquoi ne pouvons-nous pas utiliser float ou double à la place?
Erran Morad
20
@Borat Sagdiyev C'est la raison pour laquelle . , Vous pouvez également consulter ce .
Buhake Sindi
2
@Borat: vous pouvez si vous savez ce que vous faites, voir cet article de Peter Lawrey. mais il semble au moins aussi compliqué de faire tout l'arrondi que d'utiliser BigDecimals.
Nathan Hughes
35
"Si j'avais un sou pour chaque fois que j'ai vu quelqu'un utiliser FLOAT pour stocker des devises, j'aurais 999,997634 $" - Bill Karwin
Collin Krawll
36

Vous pouvez utiliser l' 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 à votre pom.xml:

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

Cette dépendance s'ajoutera de javax.money:money-apimanière transitoire 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") );
    }
}
Abdull
la source
Qu'en est-il de la sérialisation et de l'enregistrement dans la base de données? Quel format doit être utilisé pour l'envoi par fil?
Paweł Szczur
1
Je crois qu'Oracle a dédié à nouveau notamment Java Money dans Java 9. Vraiment dommage. Mais bonne réponse. Nous pouvons toujours l'utiliser avec Maven
borjab
3
Avez-vous une source pour qu'Oracle décide de ne pas inclure Java Money dans Java 9?
Abdull
25

Un type intégral représentant la plus petite valeur possible. En d'autres termes, votre programme devrait penser en centimes et non en dollars / euros.

Cela ne devrait pas vous empêcher de demander à l'interface graphique de le traduire en dollars / euros.

monstre à cliquet
la source
Gardez à l'esprit que le montant d'argent peut
dépasser
5
@eversor qui aurait besoin de plus de 20 millions de dollars, la plupart des applications n'auraient pas besoin de beaucoup si elles le font longtemps sera suffisant car même nos gouvernements ne gèrent pas assez d'argent pour déborder cela
ratchet freak
4
@ratchetfreak Il vaut probablement mieux utiliser un long alors.
trognanders
5
De nombreuses banques gèrent des sommes beaucoup plus importantes que 20 000 000 $ chaque jour. Cela ne tient même pas compte des devises comme le yen avec des taux de change élevés par rapport au dollar. Les types entiers peuvent être les meilleurs pour éviter les problèmes d'arrondi bien qu'ils soient compliqués avec les calculs des taux d'intérêt et de change. Cependant, selon l'application, vous pouvez avoir besoin d'un type entier 64 bits.
Alchymist
Idéalement des microdollars, en fait, comme si vous faisiez par exemple 10 $ / 3, l'erreur d'arrondi (3333,3 => 3333,0) n'affecte pas autant la valeur finale (dans ce cas, elle n'a pas du tout d'impact sur la valeur réelle, bien qu'elle soit dangereux de supposer qu'il ne le sera jamais). Ceci est particulièrement important si vous effectuez de nombreux calculs d'affilée avant que votre utilisateur ne voie le résultat, car les erreurs d'arrondi s'aggraveront.
Chris Browne
11

JSR 354: API Money and Currency

JSR 354 fournit une API pour représenter, transporter et 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 Money and Currency

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

  1. Une API pour gérer par exemple les montants monétaires et les devises
  2. API pour prendre en charge les implémentations interchangeables
  3. Usines de création d'instances des classes d'implémentation
  4. Fonctionnalité pour les calculs, la conversion et le formatage des montants monétaires
  5. API Java pour travailler avec Money et Devises, qui devrait être incluse dans Java 9.
  6. Toutes les classes de spécifications et interfaces se trouvent dans le package javax.money. *.

Exemples d'exemples de JSR 354: API Money and Currency:

Un exemple de création d'un MonetaryAmount et de son impression sur la console ressemble à ceci:

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

Lors de l'utilisation de 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 différentes méthodes qui permettent d'accéder à la devise attribuée, au montant numérique, à sa précision et plus encore:

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, de belles méthodes utilitaires de filtrage, de tri et de 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 MonetaryAmount personnalisées

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

Gestion de l'argent et des devises en Java avec JSR 354

Examen de l'API Java 9 Money and Currency (JSR 354)

Voir aussi: JSR 354 - Monnaie et argent

Affy
la source
Tout cela est bien, mais comme Federico l'a suggéré ci-dessus, cela semble plus lent que BigDecimal :-)) mauvaise blague alors seulement, mais je vais le tester maintenant 1 an plus tard ...
kensai
6

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

Sandeep Pathak
la source
6

j'utiliserais Joda Money

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

Liviu T.
la source
6

J'ai fait un microbenchmark (JMH) pour comparer Moneta (mise en œuvre de la devise java JSR 354) à BigDecimal en termes de performances.

Étonnamment, les performances de BigDecimal semblent meilleures que celles de moneta. J'ai utilisé la configuration moneta 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

N'hésitez pas à me corriger si quelque chose me manque

Federico Gaule Palombarani
la source
Intéressant, je vais lancer le même test avec les derniers trucs sur JDK9
kensai
4

Pour un cas simple (une devise) c'est suffisant Integer/ Long. Gardez l'argent en cents (...) ou centième / millième de cents (toute précision dont vous avez besoin avec un diviseur fixe)

Grigory Kislin
la source
3

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

Il existe de nombreux conteneurs pour la devise, mais ils utilisent tous BigDecimal comme type de données sous-jacent. Vous ne vous tromperez pas avec BigDecimal, probablement en utilisant l'arrondi BigDecimal.ROUND_HALF_EVEN.

Anthony Blake
la source
2

J'aime utiliser les petits types qui encapsulerait un double, BigDecimal ou un int comme les réponses précédentes l'ont suggéré. (J'utiliserais un double à moins que des problèmes de précision ne surviennent).

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

Garrett Smith
la source
6
Bien que j'aime aussi les petits types, vous ne devriez jamais utiliser un double pour stocker une valeur monétaire.
orien