Welchen Datentyp soll man in Java für Geld verwenden? [geschlossen]

183

Welchen Datentyp sollten Sie in Java für Geld verwenden?

questborn
quelle
2
Es hängt davon ab, welche Operationen Sie ausführen werden. Bitte bieten Sie weitere Informationen.
Eversor
@eversor Können Sie mir beschreiben, welcher Datentyp für verschiedene Vorgänge verwendet werden soll?
Questborn
1
Ich mache Berechnungen, bei denen ich Cent genau darstellen muss.
Questborn
Können Sie den größten Geldbetrag vorhersagen, den Ihre App benötigt? Und nach Ihren Berechnungen werden es einfache (Ergänzungen usw.) oder komplexere Finanzoperationen sein?
Eversor

Antworten:

133

Java hat eine CurrencyKlasse, die die ISO 4217-Währungscodes darstellt. BigDecimalist der beste Typ für die Darstellung von Währungsdezimalwerten.

Joda Money hat eine Bibliothek zur Darstellung von Geld bereitgestellt.

Buhake Sindi
quelle
5
Warum können wir nicht stattdessen float oder double verwenden?
Erran Morad
20
@ Borat Sagdiyev Dies ist der Grund warum . Außerdem können Sie sich auf diese .
Buhake Sindi
2
@ Borat: Wenn Sie wissen, was Sie tun, können Sie diesen Artikel von Peter Lawrey lesen. Aber es scheint mindestens so mühsam zu sein, alle Rundungen durchzuführen, wie BigDecimals zu verwenden.
Nathan Hughes
35
"Wenn ich für jedes Mal einen Cent hätte, wenn ich jemanden gesehen hätte, der FLOAT zum Speichern von Währungen verwendet, hätte ich 999,997634 $" - Bill Karwin
Collin Krawll
36

Sie können die Geld- und Währungs-API (JSR 354) verwenden . Sie können diese API in verwenden, sofern Sie Ihrem Projekt entsprechende Abhängigkeiten hinzufügen.

Fügen Sie für Java 8 die folgende Referenzimplementierung als Abhängigkeit zu Ihrer hinzu pom.xml:

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

Diese Abhängigkeit wird transitiv javax.money:money-apials Abhängigkeit hinzugefügt .

Sie können dann die API verwenden:

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
quelle
Was ist mit Serialisierung und Speichern in DB? Welches Format sollte für das Senden über Kabel verwendet werden?
Paweł Szczur
1
Ich glaube, dass Oracle Againts einschließlich Java Money in Java 9 gewidmet hat. Wirklich eine Schande. Aber tolle Antwort. Wir können es immer noch mit Maven
Borjab am
3
Haben Sie eine Quelle für Oracle, die sich gegen die Aufnahme von Java Money in Java 9 entscheidet?
Abdull
26

Ein ganzzahliger Typ, der den kleinstmöglichen Wert darstellt. Mit anderen Worten, Ihr Programm sollte in Cent und nicht in Dollar / Euro denken.

Dies sollte Sie nicht davon abhalten, die GUI wieder in Dollar / Euro umwandeln zu lassen.

Ratschenfreak
quelle
Denken Sie daran, dass der Geldbetrag die Größe von int
eversor
5
@eversor, der über 20 Millionen Dollar benötigen würde, die meisten Apps würden nicht so viel brauchen, wenn sie lange dauern, wird ausreichen, da nicht einmal unsere Regierungen genug Geld verarbeiten, um das zu überfluten
Ratschenfreak
4
@ratchetfreak Wahrscheinlich besser, als lange zu verwenden.
Trognanders
4
Viele Banken verwalten weitaus größere Geldsummen als 20.000.000 USD pro Tag. Dies berücksichtigt nicht einmal Währungen wie den Yen mit hohen Wechselkursen zum Dollar. Ganzzahlige Typen sind möglicherweise am besten geeignet, um Rundungsprobleme zu vermeiden, obwohl sie bei Zins- und Wechselkursberechnungen unübersichtlich werden. Abhängig von der Anwendung benötigen Sie jedoch möglicherweise einen 64-Bit-Integer-Typ.
Alchymist
Im Idealfall wirkt sich der Rundungsfehler (3333,3 => 3333,0) nicht so stark auf den Endwert aus (in diesem Fall auf den tatsächlichen Wert, obwohl dies der Fall ist) gefährlich anzunehmen, dass es niemals wird). Dies ist besonders wichtig, wenn Sie viele Berechnungen hintereinander durchführen, bevor Ihr Benutzer das Ergebnis sieht, da sich die Rundungsfehler verschlimmern.
Chris Browne
11

JSR 354: Geld- und Währungs-API

JSR 354 bietet eine API zum Darstellen, Transportieren und Durchführen umfassender Berechnungen mit Geld und Währung. Sie können es von diesem Link herunterladen:

JSR 354: Download der Geld- und Währungs-API

Die Spezifikation besteht aus folgenden Dingen:

  1. Eine API für den Umgang mit z. B. Geldbeträgen und Währungen
  2. APIs zur Unterstützung austauschbarer Implementierungen
  3. Fabriken zum Erstellen von Instanzen der Implementierungsklassen
  4. Funktionalität zur Berechnung, Umrechnung und Formatierung von Geldbeträgen
  5. Java-API für die Arbeit mit Geld und Währungen, die in Java 9 enthalten sein soll.
  6. Alle Spezifikationsklassen und Schnittstellen befinden sich im Paket javax.money. *.

Beispielbeispiele für JSR 354: Geld- und Währungs-API:

Ein Beispiel für das Erstellen eines MonetaryAmount und das Drucken auf der Konsole sieht folgendermaßen aus:

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

Bei Verwendung der Referenzimplementierungs-API ist der erforderliche Code viel einfacher:

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

Die API unterstützt auch Berechnungen mit MonetaryAmounts:

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

CurrencyUnit und MonetaryAmount

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

MonetaryAmount verfügt über verschiedene Methoden, mit denen Sie auf die zugewiesene Währung, den numerischen Betrag, ihre Genauigkeit und vieles mehr zugreifen können:

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 können mit einem Rundungsoperator gerundet werden:

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

Bei der Arbeit mit Sammlungen von MonetaryAmounts stehen einige nützliche Dienstprogrammmethoden zum Filtern, Sortieren und Gruppieren zur Verfügung.

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

Benutzerdefinierte MonetaryAmount-Operationen

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

Ressourcen:

Umgang mit Geld und Währungen in Java mit JSR 354

Einblick in die Java 9 Money and Currency API (JSR 354)

Siehe auch: JSR 354 - Währung und Geld

Affy
quelle
Das alles ist schön, aber wie Federico oben vorgeschlagen hat, sieht es nur dann langsamer aus als BigDecimal :-)) schlechter Witz, aber ich werde es jetzt 1 Jahr später testen ...
Kensai
6

Sie sollten BigDecimal verwenden , um Geldwerte darzustellen. Es ermöglicht Ihnen die Verwendung einer Vielzahl von Rundungsmodi. In Finanzanwendungen ist der Rundungsmodus häufig eine schwierige Anforderung, die möglicherweise sogar gesetzlich vorgeschrieben ist.

Sandeep Pathak
quelle
6

Ich habe ein Microbenchmark (JMH) erstellt, um Moneta (Implementierung von Java Currency JSR 354) mit BigDecimal hinsichtlich der Leistung zu vergleichen.

Überraschenderweise scheint die Leistung von BigDecimal besser zu sein als die von Moneta. Ich habe folgende Moneta-Konfiguration verwendet:

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

Ergebend

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

Bitte zögern Sie nicht, mich zu korrigieren, wenn mir etwas fehlt

Federico Gaule Palombarani
quelle
Interessant, ich werde den gleichen Test mit den neuesten Sachen auf JDK9
Kensai
4

Für den einfachen Fall (eine Währung) it'is genug Integer/ Long. Halten Sie Geld in Cent (...) oder Hundertstel / Tausendstel Cent (jede Präzision, die Sie mit festem Teiler benötigen)

Grigory Kislin
quelle
3

BigDecimal ist der beste Datentyp für die Währung.

Es gibt eine ganze Reihe von Währungscontainern, aber alle verwenden BigDecimal als zugrunde liegenden Datentyp. Mit BigDecimal werden Sie nichts falsch machen, wahrscheinlich mit BigDecimal.ROUND_HALF_EVEN-Rundung.

Anthony Blake
quelle
2

Ich benutze gerne kleine Typen die entweder ein Double, BigDecimal oder Int umschließen, wie in früheren Antworten vorgeschlagen. (Ich würde ein Double verwenden, wenn keine Präzisionsprobleme auftreten).

Ein kleiner Typ gibt Ihnen Sicherheit beim Schreiben, damit Sie ein doppeltes Geld nicht mit anderen doppelten verwechseln.

Garrett Smith
quelle
6
Auch wenn ich winzige Typen mag, sollten Sie niemals ein Double verwenden, um einen Geldwert zu speichern.
Orien