Usar BigDecimal para trabajar con monedas

Estaba intentando hacer mi propia clase para monedas usando largos, pero aparentemente debería usar BigDecimal en BigDecimal lugar. ¿Alguien podría ayudarme a comenzar? ¿Cuál sería la mejor manera de usar BigDecimal para monedas en dólares, como por ejemplo, al menos, pero no más de 2 decimales para centavos, etc. La API para BigDecimal es enorme y no sé qué métodos usar. Además, BigDecimal tiene una mejor precisión, pero ¿no se pierde todo si pasa a través de un double ? si hago un nuevo BigDecimal(24.99) , ¿cómo será diferente a usar un double ? ¿O debería usar el constructor que usa una String lugar?

Aquí hay algunos consejos:

  1. Use BigDecimal para cálculos si necesita la precisión que ofrece (los valores de Money a menudo lo necesitan).
  2. Use la clase NumberFormat para mostrar. Esta clase se ocupará de los problemas de localización de cantidades en diferentes monedas. Sin embargo, solo incluirá primitivos; por lo tanto, si puede aceptar el pequeño cambio en la precisión debido a la transformación a un double , podría usar esta clase.
  3. Al usar la clase NumberFormat , use el método scale() en la instancia de BigDecimal para establecer la precisión y el método de redondeo.

PD: en caso de que te lo estés preguntando, BigDecimal siempre es mejor que el double , cuando tienes que representar valores de dinero en Java .

PPS:

Creando instancias de BigDecimal

Esto es bastante simple ya que BigDecimal proporciona constructores para tomar valores primitivos y objetos String . Puede usar esos, preferiblemente el que toma el objeto String . Por ejemplo,

 BigDecimal modelVal = new BigDecimal("24.455"); BigDecimal displayVal = modelVal.setScale(2, RoundingMode.HALF_EVEN); 

Mostrar instancias de BigDecimal

Puede usar las llamadas al método setMinimumFractionDigits y setMaximumFractionDigits para restringir la cantidad de datos que se muestran.

 NumberFormat usdCostFormat = NumberFormat.getCurrencyInstance(Locale.US); usdCostFormat.setMinimumFractionDigits( 1 ); usdCostFormat.setMaximumFractionDigits( 2 ); System.out.println( usdCostFormat.format(displayVal.doubleValue()) ); 

Recomendaría una pequeña investigación sobre Money Pattern. Martin Fowler en su libro Análisis de patrón ha cubierto esto con más detalle.

 public class Money { private static final Currency USD = Currency.getInstance("USD"); private static final RoundingMode DEFAULT_ROUNDING = RoundingMode.HALF_EVEN; private final BigDecimal amount; private final Currency currency; public static Money dollars(BigDecimal amount) { return new Money(amount, USD); } Money(BigDecimal amount, Currency currency) { this(amount, currency, DEFAULT_ROUNDING); } Money(BigDecimal amount, Currency currency, RoundingMode rounding) { this.currency = currency; this.amount = amount.setScale(currency.getDefaultFractionDigits(), rounding); } public BigDecimal getAmount() { return amount; } public Currency getCurrency() { return currency; } @Override public String toString() { return getCurrency().getSymbol() + " " + getAmount(); } public String toString(Locale locale) { return getCurrency().getSymbol(locale) + " " + getAmount(); } } 

Llegando al uso:

Representarías todos los dineros usando el objeto Money en lugar de BigDecimal . Representar dinero como decimal grande significa que tendrá que formatear el dinero en todos los lugares donde lo muestre. Imagínese si el estándar de visualización cambia. Tendrás que hacer las ediciones por todos lados. En lugar de utilizar el patrón Money , centraliza el formato del dinero en una única ubicación.

 Money price = Money.dollars(38.28); System.out.println(price); 

O bien, espere a JSR-354 . Java Money y Currency API próximamente

1) Si está limitado a la double precisión, una razón para utilizar BigDecimal es realizar operaciones con los BigDecimal creados a partir de la double s.

2) El BigDecimal consiste en un valor sin escalar entero arbitrario de precisión y una escala entera no negativa de 32 bits, mientras que el doble ajusta un valor del double tipo primitivo en un objeto. Un objeto de tipo Double contiene un solo campo cuyo tipo es double

3) No debería hacer ninguna diferencia

No deberías tener dificultades con el $ y la precisión. Una forma de hacerlo es usando System.out.printf

Los tipos numéricos primitivos son útiles para almacenar valores individuales en la memoria. Pero cuando se trata de calcular utilizando tipos doble y flotante, hay un problema con el redondeo. Esto sucede porque la representación de la memoria no se corresponde exactamente con el valor. Por ejemplo, se supone que un valor doble toma 64 bits pero Java no usa todos los 64 bits. Solo almacena lo que cree que son las partes importantes del número. Por lo tanto, puede llegar a los valores incorrectos al agregar valores del tipo flotante o doble.

Por favor, vea un breve clip https://youtu.be/EXxUSz9x7BM

Utilice BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP) cuando desee redondear hasta los dos puntos decimales para centavos. Sin embargo, tenga en cuenta el error de redondeo cuando realice los cálculos. Debe ser coherente cuando redondee el valor del dinero. Haga el redondeo justo al final una sola vez después de hacer todos los cálculos, o aplique redondeo a cada valor antes de hacer cualquier cálculo. Cuál usar dependerá de los requerimientos de su negocio, pero en general, creo que hacer el redondeo al final parece tener un mejor sentido para mí.

Use una String cuando construya BigDecimal por valor monetario. Si usa el double , tendrá un valor de punto flotante al final. Esto se debe a la architecture de la computadora con respecto a cómo se representan los valores double / float en formato binario.

Hay un amplio ejemplo de cómo hacer esto en javapractices.com. Consulte en particular la clase Money , que pretende hacer cálculos monetarios más simples que usar BigDecimal directamente.

El diseño de esta clase Money tiene como objective hacer que las expresiones sean más naturales. Por ejemplo:

 if ( amount.lt(hundred) ) { cost = amount.times(price); } 

La herramienta WEB4J tiene una clase similar, llamada Decimal , que es un poco más pulida que la clase Money .

 NumberFormat.getNumberInstance(java.util.Locale.US).format(num);