Artículos de Tecnología > Programación

Redondeo y números gigantes: de double a BigDecimal

Paulo Silveira

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.

https://i2.wp.com/blog.caelum.com.br/wp-content/uploads/2010/07/numbers.jpeg?resize=270%2C300&ssl=1" target="_blank" rel="noopener" loading="lazy

¿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 BigDecimal: 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:

Artículos de Tecnología > Programación

En Alura encontrarás variados cursos sobre Programación. ¡Comienza ahora!

Precios en:
USD
  • USD
  • BOB
  • CLP
  • COP
  • USD
  • PEN
  • MXN
  • UYU

Semestral

  • 273 cursos

    Cursos de Programación, Front End, Data Science, Innovación y Gestión.

  • Videos y actividades 100% en Español
  • Certificado de participación
  • Estudia las 24 horas, los 7 días de la semana
  • Foro y comunidad exclusiva para resolver tus dudas
  • Luri, la inteligencia artificial de Alura

    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

  • Acceso a todo el contenido de la plataforma por 6 meses
US$ 65.90
un solo pago de US$ 65.90
¡QUIERO EMPEZAR A ESTUDIAR!

Paga en moneda local en los siguientes países

Anual

  • 273 cursos

    Cursos de Programación, Front End, Data Science, Innovación y Gestión.

  • Videos y actividades 100% en Español
  • Certificado de participación
  • Estudia las 24 horas, los 7 días de la semana
  • Foro y comunidad exclusiva para resolver tus dudas
  • Luri, la inteligencia artificial de Alura

    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

  • Acceso a todo el contenido de la plataforma por 12 meses
US$ 99.90
un solo pago de US$ 99.90
¡QUIERO EMPEZAR A ESTUDIAR!

Paga en moneda local en los siguientes países

Acceso a todos
los cursos

Estudia las 24 horas,
dónde y cuándo quieras

Nuevos cursos
cada semana