Quale tipo di dati usare per soldi in Java? [chiuso]


183

Quale tipo di dati dovresti usare per soldi in Java?


2
Dipende dalle operazioni che farai. Per favore offri maggiori informazioni
eversor

@eversor Puoi darmi una descrizione di quale tipo di dati dovrebbe essere usato per diverse operazioni?
questborn il

1
Sto facendo dei calcoli che mi richiedono di rappresentare accuratamente i centesimi.
questborn il

Sei in grado di prevedere la più grande quantità di denaro che la tua app dovrà gestire? E i tuoi calcoli saranno semplici (adizioni, ecc.) O operazioni finanziarie più complesse?
eversor

Risposte:


133

Java ha una Currencyclasse 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.


5
Perché non possiamo usare float o double invece?
Erran Morad,

20
@Borat Sagdiyev Questo è il motivo . Inoltre, puoi fare riferimento a questo .
Buhake Sindi,

2
@Borat: puoi sapere se stai facendo ciò, vedi questo articolo di Peter Lawrey. ma sembra almeno una seccatura fare tutti gli arrotondamenti come usare BigDecimals.
Nathan Hughes,

35
"Se avessi un centesimo per ogni volta che vedevo qualcuno usare FLOAT per conservare la valuta, avrei $ 999.997634" - Bill Karwin
Collin Krawll,

36

È 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-apicome 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") );
    }
}

Che dire della serializzazione e del salvataggio in db? Quale formato utilizzare per l'invio tramite filo?
Paweł Szczur,

1
Credo che Oracle abbia dedicato altri contenuti tra cui Java Money in Java 9. Davvero un peccato. Ma ottima risposta. Possiamo ancora usarlo con Maven
borjab il

3
Hai una fonte per Oracle che decide di non includere Java Money in Java 9?
Abdull

25

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.


Tieni presente che la quantità di denaro può traboccare le dimensioni di
Int

5
@eversor che avrebbe bisogno di oltre 20 milioni di dollari la maggior parte delle app non avrebbe bisogno di così tanto se lo fanno a lungo sarà sufficiente in quanto nemmeno i nostri governi gestiscono abbastanza denaro per traboccare questo
maniaco del cricchetto

4
@ratchetfreak Probabilmente meglio usare a lungo allora.
Trognandro

5
Molte banche gestiscono somme di denaro molto maggiori di $ 20.000.000 ogni giorno. Ciò non tiene nemmeno conto delle valute come lo yen con grandi tassi di cambio rispetto al dollaro. I tipi di numeri interi possono essere la soluzione migliore per evitare problemi di arrotondamento, anche se si complicano i calcoli degli interessi e dei tassi di cambio. Tuttavia, a seconda dell'applicazione, potrebbe essere necessario un tipo intero a 64 bit.
Alchymist,

Idealmente i microdollari, in realtà, come in questo caso, ad esempio $ 10/3, l'errore di arrotondamento (3333.3 => 3333.0) non influisce sul valore finale (in questo caso non influisce affatto sul valore reale, sebbene sia pericoloso supporre che non lo farà mai). Questo è particolarmente importante se stai facendo molti calcoli di fila prima che l'utente veda il risultato, poiché gli errori di arrotondamento aumenteranno.
Chris Browne,


11

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:

  1. Un'API per la gestione, ad esempio, di importi e valute monetarie
  2. API per supportare implementazioni intercambiabili
  3. Fabbriche per la creazione di istanze delle classi di implementazione
  4. Funzionalità per calcoli, conversione e formattazione di importi monetari
  5. API Java per lavorare con Money and Currencies, che dovrebbe essere inclusa in Java 9.
  6. 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


Tutto questo è bello, ma come ha suggerito Federico sopra, sembra più lento di BigDecimal :-)) brutto scherzo solo allora, ma lo farò test ora 1 anno dopo ...
Kensai,

6

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.



6

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


Interessante, eseguirò lo stesso test con le ultime cose su JDK9
kensai il

4

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)


3

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.


2

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.


6
Anche se anche a me piacciono i tipi minuscoli, non dovresti mai usare un doppio per memorizzare un valore monetario.
orien,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.