Quale tipo di dati dovresti usare per soldi in Java?
Quale tipo di dati dovresti usare per soldi in Java?
Risposte:
Java ha una Currency
classe che rappresenta i codici valuta ISO 4217.
BigDecimal
è il tipo migliore per rappresentare i valori decimali della valuta.
Joda Money ha fornito una biblioteca per rappresentare il denaro.
È possibile utilizzare l' API Money and Currency (JSR 354) . È possibile utilizzare questa API in, purché si aggiungano dipendenze appropriate al progetto.
Per Java 8, aggiungere la seguente implementazione di riferimento come dipendenza al proprio pom.xml
:
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.0</version>
</dependency>
Questa dipendenza si aggiungerà transitivamente javax.money:money-api
come dipendenza.
È quindi possibile utilizzare 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") );
}
}
Un tipo integrale che rappresenta il valore più piccolo possibile. In altre parole, il tuo programma dovrebbe pensare in centesimi non in dollari / euro.
Questo non dovrebbe impedirti di far tradurre la GUI in dollari / euro.
BigDecimal può essere usato, una buona spiegazione del perché non usare Float o Double può essere vista qui: Perché non usare Double o Float per rappresentare la valuta?
JSR 354: API per denaro e valuta
JSR 354 fornisce un'API per la rappresentazione, il trasporto e l'esecuzione di calcoli completi con denaro e valuta. Puoi scaricarlo da questo link:
JSR 354: Download dell'API per denaro e valuta
Le specifiche sono le seguenti:
- Un'API per la gestione, ad esempio, di importi e valute monetarie
- API per supportare implementazioni intercambiabili
- Fabbriche per la creazione di istanze delle classi di implementazione
- Funzionalità per calcoli, conversione e formattazione di importi monetari
- API Java per lavorare con Money and Currencies, che dovrebbe essere inclusa in Java 9.
- Tutte le classi e le interfacce delle specifiche si trovano nel pacchetto javax.money. *.
Esempi di esempio di JSR 354: API denaro e valuta:
Un esempio di creazione di MonetaryAmount e di stampa sulla console è simile al seguente:
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));
Quando si utilizza l'API di implementazione di riferimento, il codice necessario è molto più semplice:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
L'API supporta anche i calcoli con MonetaryAmounts:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));
CurrencyUnit e MonetaryAmount
// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MonetaryAmount ha vari metodi che consentono di accedere alla valuta assegnata, all'importo numerico, alla sua precisione e altro:
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;
Gli importi monetari possono essere arrotondati utilizzando un operatore di arrotondamento:
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
Quando si lavora con raccolte di MonetaryAmounts, sono disponibili alcuni metodi di utilità utili per filtrare, ordinare e raggruppare.
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"));
Operazioni MonetaryAmount personalizzate
// 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
risorse:
Gestire denaro e valute in Java con JSR 354
Esame dell'API Money and Currency di Java 9 (JSR 354)
Vedi anche: JSR 354 - Valuta e denaro
Dovresti utilizzare BigDecimal per rappresentare i valori monetari. Ti consente di utilizzare una varietà di modalità di arrotondamento e, nelle applicazioni finanziarie, la modalità di arrotondamento è spesso un requisito difficile che può anche essere richiesto dalla legge.
Vorrei usare Joda Money
È ancora alla versione 0.6 ma sembra molto promettente
Ho fatto un microbenchmark (JMH) per confrontare Moneta (implementazione JSR in valuta JSR 354) con BigDecimal in termini di prestazioni.
Sorprendentemente, le prestazioni di BigDecimal sembrano essere migliori di quelle di moneta. Ho usato la seguente moneta config:
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);
}
}
Con il risultato di
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
Sentiti libero di correggermi se mi manca qualcosa
Per un caso semplice (una valuta) è sufficiente Integer
/ Long
. Conserva il denaro in centesimi (...) o centesimo / millesimo di centesimi (qualsiasi precisione di cui hai bisogno con un divisore fisso)
BigDecimal è il miglior tipo di dati da utilizzare per la valuta.
Esistono molti contenitori per la valuta, ma usano tutti BigDecimal come tipo di dati sottostante. Non sbaglierai con BigDecimal, probabilmente usando l'arrotondamento BigDecimal.ROUND_HALF_EVEN.
Mi piace usare Tiny Type che racchiuderebbe un doppio, BigDecimal o int come suggerito dalle risposte precedenti. (Userei un doppio a meno che non sorgano problemi di precisione).
Un tipo piccolo ti dà la sicurezza del tipo in modo da non confondere un doppio denaro con altri doppi.