Por que usar BigDecimal (y no Double) para calculos aritmeticos financieros

Escribe el siguiente código:

double unCentavo = 0.01;
double suma=unCentavo+unCentavo+unCentavo+unCentavo+unCentavo+unCentavo;
System.out.println(suma);

Que se imprime? Si contestaste: 0.06, estas equivocado.

Se imprime: 0.060000000000000005

Ahora escribe:

java.math.BigDecimal unCentavo = new java.math.BigDecimal("0.01");
java.math.BigDecimal suma=unCentavo.add(unCentavo).add(unCentavo).add(unCentavo).add(unCentavo).add(unCentavo);
System.out.println(suma);

Que imprime? 0.06.

Las computadoras no cuentan como nosotros, las computadoras "cuentan en binario", nosotros, contamos en decimal. Cuando usamos una variable float o double, estamos dejandole al microprocesador de la computadora el trabajo de efectuar los cálculos directamente (con su coprocesador matemático) pero como las computadoras "piensan" en binario, cometen errores de precisión diferentes a los nuestros. Por ejemplo, nosotros los humanos, tenemos un problema para representar a la tercera parte de "1" (1/3), y la escribimos: 0.3333333333333333333... (hasta el infinito), en nuestro sistema decimal (base 10), no hay modo de representar a un tercio de un entero. Si por otro lado, nuestro sistema fuera base "9", entonces representariamos la tercera parte de un entero simplemente con : "0.3" (y seria mas preciso que cualquier lista larga de números "3" en base 10.

En binario (base 2), no se puede representar a 1/10 (la décima parte de 1) ni a 1/100 (la centésima parte de 1) y por eso, cuando nosotros escribimos "0.01" la computadora lo entiende como 1100110011001100110011001100110011... (hasta el infinito) y efectuá el calculo incorrectamente (desde nuestro punto de vista "decimal").

Si hacen sistemas financieros, los pequeños errores de calculo acumulados con varios centavos pueden resultar en reportes cuyos cálculos diferirán de las operaciones que normalmente realizamos los humanos. Para resolver este problema, en Java se incluye la clase BigDecimal, que si bien realiza los cálculos mas lento que "Double/double" los realiza del mismo modo que los humanos (en base 10) y así evitamos el problema.

Algunos microprocesadores especializados si son capaces de efectuar "por hardware" los cálculos de forma decimal. Desafortunadamente, hasta donde se, ninguno de los microprocesadores comúnmente encontrados en equipos de escritorio cuentan con esta capacidad.

Finalmente, les recomiendo este articulo, en donde se explica de forma muy detallada y clara por que es importante utilizar cálculos en decimal

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de jali

Excelente aporte

Algo asi me paso cuando hacia un sistema contable jeje y no sabia porque no cuadraban los reportes. Todo por el tipo de dato

Saludos

Imagen de ezamudio

Closures

En Java 7 ya vamos a tener closures y no sé qué tantas monerías pero no han hecho nada por arreglar double... supongo que para no "romper" la compatibilidad con versiones anteriores, o sea arreglarla y que repente las apps sumen bien los floats y doubles, pero como seguramente las apps hacen algo para parchar eso, entonces van a salir mal las sumas...

Imagen de benek

Buen tip

Que interesante saberlo, muy buen tip. Supongo que ha de ser el dolor de cabeza de muchos desarrolladores a los que no les salen las cuentas.

Saludos.

Imagen de javadicto

Gracias por el aporte,

Gracias por el aporte, apenas genere unos reportes usando POI y precisamente realizaba operaciones aritmeticas con decimales y tube este problema.

Imagen de gorlok

Excelente artículo. Es un

Excelente artículo. Es un error muy común, conozco proyectos que descubrieron el problema ya a mitad del desarrollo, lo que puede resultar en un costo de rework altísimo en el peor de los casos. Es muy importante tenerlo siempre presente, y desde el arranque. Saludos

Es cierto...

En Interés compuesto con Java me falto la función para redondear la cantidad resultado.. no sabia esto

Imagen de 1a1iux

Muy buena

explicación, muy clara.

Imagen de Nopalin

Escala

Fijate que yo ya habia encontrado este problema y no se me habia ocurrido postearlo jeje.

Bueno para aquellos que trabajen con hibernate, les recomiendo que en las propiedades de tipo BigDecimal pongan la escala, ya que por defecto hibernate da 2 decimales y eso al menos a mi me ha ocacionado algunos problemitas, yo personalmente siempre uso 6.

@Column(nullable = false, precision = 19, scale = 6)
BigDecimal importe;

Saludos

Imagen de luxspes

En Java 7 ya vamos a tener

En Java 7 ya vamos a tener closures y no sé qué tantas monerías pero no han hecho nada por arreglar double...

Exacto, sigue asi por compatiblidad hacia atras, pero seria bueno agregaran algo como el "decimal" de C# para poder hacer operaciones precisas usando operadores... (aunque, por supuesto, ya es posible hacerlo en Groovy, en Clojure ...y creo que tambien en Scala (no encontre ejemplos, pero Scala soporta operator overloading) )

Imagen de benek

Floating Point

Encontré vía @cesarfp un sitio para ahondar en el tema: http://floating-point-gui.de/languages/java/

Saludos.

Javier Ramírez Jr.
http://twitter.com/_benek

Problemas con double

He tenido demasiado problemas en un sistema de ventas, al final he descubierto lo de los tipos double, asi que, ahora a cambiar todo a BigDecimal

Moraleja; Usar si o si bigdecimal para calculos con decimales.

Float y Double

Yo uso así:

double uncent=0.01;
float usuma=0;
usuma=Float.parseFloat(Double.toString(uncent+uncent+uncent+uncent+uncent+uncent));
System.out.println(usuma);

Esto esta mal?

Imagen de bferro

No es un problema de compatibilidad hacia atrás

Mantener el tipo double, más que satisfacer la compatibilidad hacia atrás es una necesidad de los lenguajes de programación para dar soporte a la aritmética de punto flotante usando el estándar IEEE 754, que se necesita en muchas aplicaciones.
Por supuesto que los problemas de precisión y de la imposibilidad de una representación exacta de un decimal en el formato de IEEE 754 o de cualquier otro formato de punto flotante, son conocidas y de ahí la necesidad que siempre ha habido de utilizar aritmética de precisión arbitraria para resolver problemas como los que aquí se mencionan.
Todos los lenguajes de programación tienen bien soporte built in para eso o se acompañan de bibliotecas de funciones para realizar aritmética decimal.
Java lo hace con BigDecimal, aunque también existen otras bibliotecas para finanzas para resolver esos problemas.

Imagen de sanlegas

Bueno

No se si sea el lugar adecuado ,pero tengo un problema parecido(eso pienso) sobre unas funciones trigonometricas, alguien podria ayudarme?
http://www.javamexico.org/foros/java_standard_edition/angulos_funciones_...

Problemas con Sumas y restas

Hola a todos,
Me estoy volviendo loca con un problema. Ya hace tiempo cambiamos todas las operaciones para que se hicieran a traves de BigDecimal, pero aun así nos siguen dando quebraderos de cabeza y no encuentro a nadie que les haya pasa algo parecido. Os cuento mi problema a ver s a alguien se le ocurre una solución.

Tengo dos cifras 0.7251 y -0.0001 quiero sumarlas y redondearlas a dos decimales esperando que el resultado sea 0.73 pero no hay manera, siempre da 0.72 ¿por qué? pues ese es el problema,que no se porque. Este es el código:

BigDecimal num1 = new BigDecimal("0.7251");
BigDecimal num2 = new BigDecimal("-0.0001");
BigDecimal result = num1.add(num2);//el resultado es 0.725
result=result.setScale(2, BigDecimal.ROUND_HALF_UP);
return new Double(result.doubleValue());//retorna 0.72?¿?¿?

Como curiosidad os diré que si ejecuto esto:

BigDecimal num1 = new BigDecimal("0.7251");
BigDecimal num2 = new BigDecimal("0.0001");
BigDecimal result = num1.subtract(num2);//el resultado es 0.7250
result=result.setScale(2, BigDecimal.ROUND_HALF_UP);
return new Double(result.doubleValue());//retorna 0.73?¿?¿?

¿Alguien sabe por qué se comporta distinto si sumo un numero positivo y uno negativo y cuando resto el negativo en valor absoluto al positivo? (estoy usando jre1.5.0_15, por si cambia mucho entre versiones)

Imagen de ezamudio

RoundingMode

Lee acerca del RoundingMode. Que por cierto ya no se recomienda usar las constantes enteras de BigDecimal para redondeo sino las constantes de RoundingMode. El modo que estás usando, ROUND_HALF_UP, se porta según su especificación; que no entiendas por qué pasa eso es señal de que no leíste la documentación de BigDecimal.setScale ni de los modos de redondeo y solamente usaste el primero que se te ocurrió, sin saber por qué.

Yo no le he faltado al

Yo no le he faltado al respeto a nadie, asi que por favor no lo hagas tu, Evidentemente hay algo que no he entendido de la documentacion, dado que estoy harta de relerla y por eso pido ayuda en un foro. Si no tuviera problemas no tendria que preguntarlos.

Ya habia ledio acercar del RoundingMode y por eso sigo sin entender por que 0.725 redondeado da 0.72 y con el mismo redondeo 0.7250 da 0.73 ya que,a mi al menos, la lógica del ROUND_HALF_UP me dice que los dos deberian dar 0.73

Imagen de ezamudio

HALF_UP

Tomado de RoundingMode.HALF_UP:

Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up. Behaves as for RoundingMode.UP if the discarded fraction is >= 0.5; otherwise, behaves as for RoundingMode.DOWN. Note that this is the rounding mode commonly taught at school.

En español:

Modo de redondeo hacia el vecino más cercano, a menos que ambos vecinos sean equidistantes, en cuyo caso se redondea hacia arriba. Se comporta como UP si la fracción descartada es mayor o igual a 0.5; de lo contrario se comporta como DOWN. Es el modo de redondeo comúnmente enseñado en las escuelas.

Y luego viene una tablita ilustrando algunos ejemplos. Pero siguiendo la descripción, 0.725 se debe redondear a 0.73, que es lo que te sucede en ambos casos (revisa tu variable result y verás que siempre es 0.73). Tu problema es que luego lo conviertes a Double. Lo irónico es que describes tu problema como comentario de un post titulado "por qué usar bigdecimal y no double para cálculos financieros".