Java 50: BigDecimal — Money, Precision & RoundingMode
easy⏱ 5 mincoursejava
Por qué double falla para dinero
double es IEEE-754 binario: 0.1 no tiene representación binaria finita, así que 0.1 + 0.2 da 0.30000000000000004. Acumular esos errores sobre un libro mayor desvía por centavos. BigDecimal guarda un valor sin escala exacto (BigInteger) y una scale (número de decimales), así 0.1 es exactamente 0.1. Nunca uses double/float para moneda.
System.out.println(0.1 + 0.2); // 0.30000000000000004
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b)); // 0.3 (exact)
Construye desde String, nunca desde double
new BigDecimal(0.1) captura el error del double: 0.1000000000000000055511.... new BigDecimal("0.1") es exactamente 0.1. Construye siempre BigDecimal desde un String (o BigDecimal.valueOf(double), que pasa por Double.toString). La igualdad también es una trampa: equals compara también la escala, así 2.0 ≠ 2.00; usa compareTo() == 0 para igualdad numérica.
RoundingMode y scale
La división puede no terminar, así que divide requiere una escala + RoundingMode o lanza ArithmeticException. HALF_UP redondea .5 alejándose de cero (redondeo escolar). HALF_EVEN (redondeo bancario) redondea .5 al dígito par más cercano — es el default para dinero porque elimina el sesgo sistemático al alza de redondear siempre .5 hacia arriba sobre millones de transacciones. setScale(2, HALF_EVEN) ajusta a centavos.
BigDecimal price = new BigDecimal("10.005");
price.setScale(2, RoundingMode.HALF_UP); // 10.01
price.setScale(2, RoundingMode.HALF_EVEN); // 10.00 (round to even)
Aritmética de dinero exacta
(1) Demuestra el bug: imprime que 0.1+0.2 como double NO es 0.3, pero la suma decimal exacta da exactamente 0.3. (2) Suma una lista de precios [19.99, 5.01, 0.10, 0.20] exactamente a 25.30. (3) Divide una cuenta de $10.00 entre 3 con HALF_EVEN a centavos y muestra que el centavo restante se asigna (para que las partes sumen de vuelta 10.00). Imprime los tres resultados.
Guarda dinero como centavos enteros cuando puedas
Si tu dominio nunca necesita precisión por debajo del centavo, guarda dinero como un long de unidades menores (centavos) y formatea solo en la salida. Es más rápido que BigDecimal, sin sorpresas de modo de redondeo en la aritmética, y esquiva la trampa equals-vs-compareTo. Usa BigDecimal cuando necesites escala configurable, cambio de divisa o fracciones de impuestos.
Quiz: equals vs compareTo en BigDecimal
¿Es new BigDecimal("2.0").equals(new BigDecimal("2.00")) true? No — equals también compara la escala (1 vs 2 decimales), así que son desiguales aunque sean numéricamente iguales. Usa a.compareTo(b) == 0 para igualdad de valor. Esta sutileza muerde cuando los BigDecimal se usan como claves de HashMap o en assertEquals.