¿Qué tipo de datos usar para dinero en Java? [cerrado]

183

¿Qué tipo de datos debería usar para dinero en Java?

questborn
fuente
2
Depende de las operaciones que va a hacer. Por favor ofrezca más información.
eversor
@eversor ¿Puede darme una descripción de qué tipo de datos debe usarse para diferentes operaciones?
questborn
1
Estoy haciendo cálculos que me obligan a representar centavos con precisión.
questborn
¿Puede predecir la mayor cantidad de dinero que su aplicación necesitará manejar? Y, según sus cálculos, ¿serán operaciones financieras simples (adiciones, etc.) o más complejas?
eversor

Respuestas:

133

Java tiene una Currencyclase que representa los códigos de moneda ISO 4217. BigDecimales el mejor tipo para representar valores decimales de moneda.

Joda Money ha proporcionado una biblioteca para representar el dinero.

Buhake Sindi
fuente
55
¿Por qué no podemos usar float o double en su lugar?
Erran Morad
20
@Borat Sagdiyev Esta es la razón por la cual . Además, puede consultar esto .
Buhake Sindi
2
@Borat: puedes saber si sabes lo que estás haciendo, ver este artículo de Peter Lawrey. pero parece al menos una molestia tan grande hacer todo el redondeo como usar BigDecimals.
Nathan Hughes
35
"Si tuviera un centavo por cada vez que he visto a alguien usar FLOAT para almacenar divisas, tendría $ 999.997634" - Bill Karwin
Collin Krawll
36

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-apicomo 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") );
    }
}
Abdull
fuente
¿Qué pasa con la serialización y guardar en db? ¿Qué formato se debe usar para enviar por cable?
Paweł Szczur
1
Creo que Oracle volvió a declarar que incluye Java Money en Java 9. Realmente una pena. Pero gran respuesta. Todavía podemos usarlo con Maven
borjab
3
¿Tiene una fuente para que Oracle decida no incluir Java Money en Java 9?
Abdull
25

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.

monstruo de trinquete
fuente
Tenga en cuenta que la cantidad de dinero que puede desbordar el tamaño de int
Eversor
55
@eversor que necesitaría más de 20 millones de dólares la mayoría de las aplicaciones no necesitan mucho si lo hacen mucho será suficiente como ni siquiera nuestros govenrments manejan el dinero de C suficiente para que el desbordamiento
trinquete monstruo
44
@ratchetfreak Probablemente mejor usar un largo entonces.
trognanders
55
Muchos bancos manejan sumas de dinero mucho mayores que $ 20,000,000 cada día. Esto ni siquiera tiene en cuenta monedas como el yen con grandes tipos de cambio por dólar. Los tipos enteros pueden ser mejores para evitar problemas de redondeo, aunque se complican con los cálculos de interés y tipo de cambio. Sin embargo, dependiendo de la aplicación, es posible que necesite un tipo entero de 64 bits.
Alchymist
Idealmente, los microdólares, en realidad, como entonces, si lo hace, por ejemplo, $ 10/3, entonces el error de redondeo (3333.3 => 3333.0) no afecta tanto el valor final (en este caso no afecta el valor real en absoluto, aunque es peligroso asumir que nunca lo hará). Esto es especialmente importante si está haciendo muchos cálculos seguidos antes de que su usuario vea el resultado, ya que los errores de redondeo se agravarán.
Chris Browne
11

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:

  1. Una API para manejar, por ejemplo, montos monetarios y monedas
  2. API para soportar implementaciones intercambiables
  3. Fábricas para crear instancias de las clases de implementación
  4. Funcionalidad para cálculos, conversión y formateo de importes monetarios.
  5. API de Java para trabajar con dinero y monedas, que se planea incluir en Java 9.
  6. 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

Affy
fuente
Todo esto está muy bien, pero a medida que Federico se sugirió anteriormente, parece más lento que BigDecimal :-)) mala broma sólo entonces, pero voy a darle probar ahora 1 año después ...
kensai
6

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.

Sandeep Pathak
fuente
6

Yo usaría Joda Money

Todavía está en la versión 0.6 pero parece muy prometedor

Liviu T.
fuente
6

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

Federico Gaule Palombarani
fuente
Interesante, haré la misma prueba con las últimas cosas en JDK9
kensai
4

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)

Grigory Kislin
fuente
3

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.

Anthony Blake
fuente
2

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.

Garrett Smith
fuente
66
Si bien a mí también me gustan los tipos pequeños, nunca debes usar un doble para almacenar un valor monetario.
orien