¿Qué tipo de datos debería usar para dinero en Java?
¿Qué tipo de datos debería usar para dinero en Java?
Respuestas:
Java tiene una Currency
clase que representa los códigos de moneda ISO 4217.
BigDecimal
es el mejor tipo para representar valores decimales de moneda.
Joda Money ha proporcionado una biblioteca para representar el dinero.
Puede usar la API de dinero y divisas (JSR 354) . Puede usar esta API, siempre que agregue las 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 se agregará transitivamente javax.money:money-api
como una dependencia.
Luego puede usar 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") );
}
}
Un tipo integral que representa el valor más pequeño posible. En otras palabras, su programa debería pensar en centavos, no en dólares / euros.
Esto no debería impedir que la interfaz gráfica lo traduzca nuevamente a dólares / euros.
Se puede usar BigDecimal, aquí se puede ver una buena explicación de por qué no usar Float o Double: ¿Por qué no usar Double o Float para representar la moneda?
JSR 354: API de dinero y divisas
JSR 354 proporciona una API para representar, transportar y realizar cálculos completos con Money and Currency. Puedes descargarlo desde este enlace:
JSR 354: Descarga de API de dinero y divisas
La especificación consta de lo siguiente:
- Una API para manejar, por ejemplo, montos monetarios y monedas
- API para soportar implementaciones intercambiables
- Fábricas para crear instancias de las clases de implementación
- Funcionalidad para cálculos, conversión y formateo de importes monetarios.
- API de Java para trabajar con dinero y monedas, que se planea incluir en Java 9.
- Todas las clases e interfaces de especificación se encuentran en el paquete javax.money. *.
Ejemplos de ejemplo de JSR 354: API de dinero y divisas:
Un ejemplo de crear una MonetaryAmount e imprimirlo en la consola se ve así:
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 se usa 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));
La API también admite 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 acceder a la 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;
Las cantidades monetarias se pueden redondear utilizando 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 Montos Monetarios, hay disponibles algunos buenos métodos de utilidad para filtrar, ordenar y agrupar.
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"));
Operaciones de Monto Monetario Personalizado
// 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:
Manejo de dinero y monedas en Java con JSR 354
Analizando la API de dinero y moneda de Java 9 (JSR 354)
Ver también: JSR 354 - Moneda y dinero
Debe usar BigDecimal para representar valores monetarios. Le permite usar 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 obligatorio por ley.
Yo usaría Joda Money
Todavía está en la versión 0.6 pero parece muy prometedor
He hecho un microbenchmark (JMH) para comparar Moneta (implementación de JSR 354 en moneda java) con BigDecimal en términos de rendimiento.
Sorprendentemente, el rendimiento de BigDecimal parece ser mejor que el de moneta. He usado la siguiente configuración de 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
Por favor, siéntase libre de corregirme si me falta algo
Para un caso simple (una moneda) es suficiente Integer
/ Long
. Mantenga el dinero en centavos (...) o en centésimas / milésimas de centavo (cualquier precisión que necesite con un divisor fijo)
BigDecimal es el mejor tipo de datos para usar para la moneda.
Hay muchos contenedores para la moneda, pero todos usan BigDecimal como el tipo de datos subyacente. No se equivocará con BigDecimal, probablemente con el redondeo BigDecimal.ROUND_HALF_EVEN.
Me gusta usar los tipos pequeños que envolvería un doble, BigDecimal o int como las respuestas anteriores han sugerido. (Usaría un doble a menos que surjan problemas de precisión).
Un Tipo Diminuto le brinda seguridad de tipo para que no confunda un doble dinero con otros dobles.