2011-11-16 19 views
113

¿Cuál es el mejor tipo de datos para usar con dinero en la aplicación Java?¿Cuál es el mejor tipo de datos para usar con dinero en la aplicación Java?

+2

Depende de lo que las operaciones que se va a hacer. Por favor, ofrezca más información. – eversor

+0

@eversor ¿Puede darme una descripción de qué tipo de datos se debe usar para diferentes operaciones? – questborn

+1

Estoy haciendo cálculos que requieren que represente centavos con precisión. – questborn

Respuesta

94

Java tiene Currency clase que representa los códigos de moneda ISO 4217. BigDecimal es el mejor tipo para representar valores decimales monetarios.

Joda Money ha proporcionado una biblioteca para representar dinero.

+2

¿Por qué no podemos usar flotador o doble en su lugar? –

+12

@Borat Sagdiyev [Esta es la razón por la cual] (http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems). Además, puede consultar [esto] (https://www.securecoding.cert.org/confluence/display/java/NUM04-J.+Do+not+use+floating-point+numbers+if+precise+computation + es + requerido). –

+2

@Borat: puede hacerlo si sabe lo que está haciendo, vea [este artículo] (http://vanillajava.blogspot.com/2011/08/double-your-money-again.html) por Peter Lawrey. pero parece al menos una gran molestia hacer todo el redondeo en cuanto a usar BigDecimales. –

5

me gustaría utilizar Joda Money

Es todavía en la versión 0.6, pero se ve muy prometedor

1

BigDecimal es el mejor tipo de datos que se utilizará para la moneda.

Hay un montón de contenedores para moneda, pero todos usan BigDecimal como el tipo de datos subyacente. No se equivocará con BigDecimal, probablemente utilizando el redondeo BigDecimal.ROUND_HALF_EVEN.

2

Debe utilizar BigDecimal para representar valores monetarios .Se le permite utilizar una variedad de modos de redondeo y en aplicaciones financieras, el modo de redondeo es a menudo un requisito difícil que incluso puede ser obligatoria por ley .

14

Un tipo integral que representa el menor valor posible. En otras palabras, su programa debería pensar en centavos, no en dólares/euros.

Esto no debería impedir que la GUI lo traduzca de nuevo a dólares/euros.

+0

Tenga en cuenta que la cantidad de dinero puede desbordar el tamaño de int – eversor

+4

@eversor que necesitaría más de 20 millones de dólares la mayoría de las aplicaciones no necesitarían mucho si lo hacen un tiempo suficiente, ya que ni siquiera nuestros gobiernos manejan suficiente dinero para desbordamiento que –

+2

@ratchetfreak Probablemente es mejor usar un largo entonces. –

1

Me gusta usar Tiny Types que envolvería un doble, BigDecimal o int como lo sugirieron las respuestas anteriores. (Utilizaría un doble a menos que surjan problemas de precisión).

Un tipo pequeño le da seguridad de tipo para que no se confunda una moneda doble con otras dobles.

+5

Si bien me gustan los tipos pequeños, debe * Nunca * usar un doble para almacenar un valor monetario. – orien

23

Puede usar Money and Currency API (JSR 354). Se espera que esta API forme parte de Java 9. Puede usar esta API en Java 7 y Java 8, siempre que agregue dependencias apropiadas a su proyecto.

Para Java 8, agregue la siguiente implementación de referencia como una dependencia a su pom.xml:

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

Esta dependencia será transitiva añadir javax.money:money-api como dependencia.

A continuación, puede utilizar la 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

¿Qué pasa con la serialización y el almacenamiento en db? ¿Qué formato debe usarse para enviar por cable? –

+0

Creo que Oracle deformó contratítulos incluyendo Java Money en Java 9. Realmente es una pena. Pero una gran respuesta. Todavía podemos usarlo con Maven – borjab

+1

¿Tiene alguna fuente para que Oracle decida no incluir Java Money en Java 9? – Abdull

2

he hecho un microanálisis (JMH) para comparar Moneta (moneda de Java JSR 354 implementación) contra BigDecimal en términos de rendimiento.

Sorprendentemente, el rendimiento de BigDecimal parece ser mejor que el de Moneta. he utilizado siguiente config 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); 
    } 
} 

Resultando 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 

favor, no dude en corregirme si me falta algo

+0

Interesante, voy a ejecutar la misma prueba con las últimas novedades en JDK9 – kensai

7

JSR 354: dinero y Moneda API

JSR 354 proporciona una API para representar, transportar y realizar cálculos integrales con dinero y moneda. Se puede descargar desde este enlace:

JSR 354: Money and Currency API Download

La especificación consta de las siguientes cosas:

  1. Una API para el manejo de correo. gramo. importes monetarios y divisas
  2. APIs para apoyar las implementaciones intercambiables
  3. fábricas para la creación de instancias de las clases de implementación
  4. funcionalidad para los cálculos, la conversión y el formato de los importes monetarios
  5. API Java para trabajar con el dinero y las monedas, la cual es planeado para ser incluido en Java 9.
  6. Todas las clases de especificación e interfaces se encuentran en el paquete javax.money. *.

muestra ejemplos de JSR 354: dinero y la moneda API:

Un ejemplo de la creación de un MonetaryAmount e imprimirla a la consola se parece a esto ::

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

Cuando mediante la API de implementación de referencia, el código necesario es mucho más simple:

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

El API también es compatible con los cálculos con MonetaryAmounts:

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

CurrencyUnit y MonetaryAmount

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

MonetaryAmount tiene varios métodos que permiten el acceso a moneda asignada, la cantidad numérica, su precisión y más:

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 pueden redondearse usando un operador de redondeo:

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 

Al trabajar con colecciones de MonetaryAmounts, hay algunos buenos métodos de utilidad para filtrar, clasificar y agrupar disponibles.

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

MonetaryAmount personalizada

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

Recursos:

Handling money and currencies in Java with JSR 354

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

Ver también: JSR 354 - Currency and Money

+0

Todo esto es bueno, pero como Federico sugirió antes, parece más lento que BigDecimal :-)) mal chiste solo entonces, pero lo probaré ahora 1 año después. .. – kensai

1

Para el caso simple (una moneda) es suficiente Integer/Long. mantener el dinero en centavos (...) o centésimas/milésimas de centavos (cualquier precisión que necesita con el divisor fijo)

Cuestiones relacionadas