Es fácil encontrar las limitaciones del doublé
en Java y la mayoría de los otros lenguajes: cuando vamos a trabajar con dinero notamos que las cuentas no están saliendo exactamente como esperábamos:
double d1 = 0.1;
double d2 = 0.2;
System.out.println (d1 + d2);
El resultado es un extraño 0.30000000000000004, lo que puede causar serios problemas según el uso y el redondeo aplicado posteriormente a ese número. El problema es que un número con 0.1 no se puede representar en binario de una manera finita: se convierte en un diezmo (en el binario se vería como 0.110011001100…) diferente del número 0.25, que se puede representar perfectamente (en el binario 0.01). La representación es un poco más complicada que eso, JVM sigue el estándar IEEE 754 para trabajar con números de punto flotante.
¿Cómo conseguir el esperado 0.3? La sugerencia es siempre utilizar el BigDecimal
. BigDecimal
es una clase que trabaja con números de punto flotante de precisión arbitraria: puede elegir cuánta precisión desea usar. Por estándar, utilizará lo que sea necesario y, a diferencia del double, puede almacenar números como 0.1, ya que guardará esto como 1 x 10ˆ-1
(es decir, usando la base decimal en lugar de binaria, evitando el diezmo).
// no use este constructor:
BigDecimal big1 = new BigDecimal(0.1);
BigDecimal big2 = new BigDecimal(0.2);
System.out.println(big1.add(big2));
El resultado es una nueva sorpresa, una increíble 0.300000000000000016653345369377.... Lo que hicimos mal ahora fue intentar sumar 0.1 y 0.2 siendo que estos dos números ya estaban almacenados en la memoria como double, y, cuando se pasaron al constructor del BigDecimal, fueron transportados con imprecisión. El propio javadoc de este constructor dice que "Los resultados de este constructor pueden ser algo impredecibles“. De hecho el resultado es bastante predecible según sus reglas, pero no es lo que nos gustaría.
¿Cómo solucionar? Utiliza siempre el constructor que trabaja con Strings, entonces el BigDecimal hará internamente el parsing de estos números sin que se almacenen en un double, evitando problemas de precisión:
// ¡atencion! usando String en el construtor:
BigDecimal big1 = new BigDecimal("0.1");
BigDecimal big2 = new BigDecimal("0.2");
System.out.println(big1.add(big2));
Por fin obteniendo el resultado esperado. También hay observaciones importantes sobre el BigDecima
l: por estándar no hará ningún tipo de redondeo, lo que lo obliga a registrar java.lang.ArithmeticException
en el caso de un décimo decimal (intenta dividir 1/3, por ejemplo). En estos casos, es necesario limitar el número de bits que se utilizarán o elegir el modo de redondeo:
BigDecimal big1 = new BigDecimal("1");
BigDecimal big2 = new BigDecimal("3");
System.out.println(big1.divide(big2, 3, RoundingMode.UP));
Resultando en 0.334. También vale la pena recordar la inmutabilidad de la clase BigDecimal
, que tiene varias ventajas, pero se debe usar con cuidado cuando se realizarán varias operaciones en el mismo número dentro de un bucle, ya que varios BigDecimals se instanciarán durante la operación, lo que puede resultar en el mismo problema de desempeño del uso de la concatenación de Strings. Donizetti recordó que este tema se discute ampliamente en el numeral 48 de Effective Java.
En JavaScript tendremos el mismo problema si necesitas hacer cuentas en el lado del cliente, y luego podemos usar BigDecimalJS, que funciona de forma similar a Java.
Rafael Ferreira nos recuerda que podemos ir más allá, y como el dinero es algo que pertenece a nuestro dominio y lógica de negocios, creamos una clase Money
para encapsular todo este comportamiento y prevenir que RoundingMode
, MathContext
y escalas se esparzan por todo tu código.
¿Te interesa este tipo de contenido? Les invitamos a conocer la página de Alura, donde encontrarás diversos cursos de Java.
Puedes leer también:
Cursos de Programación, Front End, Data Science, Innovación y Gestión.
Luri es nuestra inteligencia artificial que resuelve dudas, da ejemplos prácticos y ayuda a profundizar aún más durante las clases. Puedes conversar con Luri hasta 100 mensajes por semana
Paga en moneda local en los siguientes países
Cursos de Programación, Front End, Data Science, Innovación y Gestión.
Luri es nuestra inteligencia artificial que resuelve dudas, da ejemplos prácticos y ayuda a profundizar aún más durante las clases. Puedes conversar con Luri hasta 100 mensajes por semana
Paga en moneda local en los siguientes países
Puedes realizar el pago de tus planes en moneda local en los siguientes países:
País | |||||||
---|---|---|---|---|---|---|---|
Plan Semestral |
487.37
BOB |
69289.36
CLP |
307472.10
COP |
65.90
USD |
264.35
PEN |
1435.53
MXN |
2978.57
UYU |
Plan Anual |
738.82
BOB |
105038.04
CLP |
466107.17
COP |
99.90
USD |
400.74
PEN |
2176.17
MXN |
4515.32
UYU |
Acceso a todos
los cursos
Estudia las 24 horas,
dónde y cuándo quieras
Nuevos cursos
cada semana