2011-11-16 10 views

risposta

94

Java ha Currency classe che rappresenta i codici di valuta ISO 4217. BigDecimal è il tipo migliore per rappresentare i valori decimali della valuta.

Joda Money ha fornito una libreria per rappresentare il denaro.

+2

Perché non possiamo usare float o double invece? –

+12

@Borat Sagdiyev [Questo è il motivo per cui] (http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems). Inoltre, puoi fare riferimento a [questo] (https://www.securecoding.cert.org/confluence/display/java/NUM04-J.+Do+not+use+floating-point+numbers+if+precise+computation + + è richiesto). –

+2

@Borat: puoi farlo se sai cosa stai facendo, leggi [questo articolo] (http://vanillajava.blogspot.com/2011/08/double-your-money-again.html) di Peter Lawrey. ma sembra almeno altrettanto problematico fare tutti gli arrotondamenti per usare BigDecimals. –

5

userei Joda Money

E 'ancora alla versione 0.6, ma sembra molto promettente

1

BigDecimal è il miglior tipo di dati da utilizzare per la valuta.

Ci sono molti contenitori per valuta, ma tutti usano BigDecimal come tipo di dati sottostante. Non sbaglierai con BigDecimal, probabilmente usando l'arrotondamento BigDecimal.ROUND_HALF_EVEN.

2

Si dovrebbe usare BigDecimal per rappresentare i valori monetari .E consente di utilizzare una serie di arrotondamento modalità, e in applicazioni finanziarie, la modalità di arrotondamento è spesso un requisito difficile che può anche essere obbligatorio per legge .

14

Un tipo integrale che rappresenta il minimo valore possibile. In altre parole, il tuo programma dovrebbe pensare in centesimi, non in dollari/euro.

Questo non dovrebbe impedirti di avere la traduzione in dollari/euro.

+0

Ricorda che la quantità di denaro può eccedere la dimensione di int – eversor

+4

@eversor che avrebbe bisogno di più di 20 milioni di dollari, la maggior parte delle app non avrebbe bisogno di così tanto se ne farebbero a lungo saranno sufficienti, perché nemmeno i nostri governi gestiscono abbastanza soldi per overflow that –

+2

@ratchetfreak Probabilmente è meglio usare un tempo allora. –

1

Mi piace l'utilizzo di Tiny Types che può includere un doppio, BigDecimal o int come suggerito dalle risposte precedenti. (Vorrei usare una doppia se non emergono problemi di precisione).

Un Tiny Type ti dà sicurezza del tipo in modo da non confondere un doppio denaro con altri doppi.

+5

Anche se mi piacciono i tipi minuscoli, dovresti * Non * usare una doppia per memorizzare un valore monetario. – orien

23

È possibile utilizzare l'API Money and Currency (JSR 354). Questa API dovrebbe essere parte di Java 9. È possibile utilizzare questa API in Java 7 e Java 8, a condizione di aggiungere dipendenze appropriate al progetto.

Per Java 8, aggiungere il seguente implementazione di riferimento come una dipendenza per il vostro pom.xml:

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

Questa dipendenza si transitivamente aggiungere javax.money:money-api come una 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")); 


    } 
} 
+0

Che ne dici di serializzazione e salvataggio in db? Quale formato deve essere usato per inviare su filo? –

+0

Credo che Oracle abbia dedicato againts incluso Java Money in Java 9. Davvero un peccato. Ma ottima risposta. Possiamo ancora usarlo con Maven – borjab

+1

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

2

Ho fatto un microbenchmark (JMH) per confrontare Moneta (moneta Java JSR 354 di attuazione) contro BigDecimal in termini di prestazioni.

Sorprendentemente, le prestazioni BigDecimal sembrano essere migliori di quelle di moneta. Ho usato seguente configurazione moneta:

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 conseguente

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 

Non esitate a correggermi se mi manca qualcosa

+0

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

7

JSR 354: denaro e valuta API

JSR 354 fornisce un'API per la rappresentazione, il trasporto e l'esecuzione di calcoli completi con denaro e valuta. Potete scaricarlo da questo link:

JSR 354: Money and Currency API Download

La specifica è costituito dai seguenti cose:

  1. un'API per la gestione di posta. g. gli importi monetari e valute
  2. API per supportare le implementazioni intercambiabili
  3. fabbriche per la creazione di istanze delle classi di implementazione
  4. funzionalità per i calcoli, la conversione e la formattazione degli importi monetari
  5. API Java per lavorare con denaro e valute, che è progettato per essere incluso in Java 9.
  6. Tutte le classi di specifiche e le interfacce si trovano nel pacchetto javax.money. *.

Esempi Esempi di JSR 354: Soldi e valuta API:

Un esempio di creazione di un MONETARYAMOUNT e la stampa alla console appare così ::

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 utilizzando 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 inoltre 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 accesso valuta assegnata, la quantità numerica, la sua precisione e più:

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; 

Monete Gli arrotondamenti 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 validi metodi di utilità per filtraggio, ordinamento e raggruppamento.

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

personalizzato 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 

Risorse:

Handling money and currencies in Java with JSR 354

Looking into the Java 9 Money and Currency API (JSR 354)

Vedi anche: JSR 354 - Currency and Money

+0

Tutto questo è bello, ma come suggerito da Federico sopra, sembra più lento di BigDecimal :-)) solo scherzo, ma lo proverò ora 1 anno dopo. .. – kensai

1

Per caso semplice (una valuta) è sufficiente Integer/Long. Mantenere il denaro in centesimi (...) o centesimo/millesimo di centesimi (qualsiasi precisione necessaria con divisore fisso)