2012-03-20 18 views
13

Sé que el siguiente comportamiento es un problema antiguo, pero aún no lo entiendo.Problemas de precisión Java BigDecimal

System.out.println(0.1 + 0.1 + 0.1);  

O a pesar de que yo uso BigDecimal

System.out.println(new BigDecimal(0.1).doubleValue() 
    + new BigDecimal(0.1).doubleValue() 
    + new BigDecimal(0.1).doubleValue()); 

Por qué este resultado es: 0.30000000000000004 en lugar de: 0.3?

¿Cómo puedo solucionar esto?

+12

... Usted no está usando 'BigDecimal'. Estás haciendo * exactamente lo mismo * que agregar los dobles. 'doubleValue()' devuelve ... un doble. Consulte el Javadoc para 'BigDecimal' sobre cómo agregar/restar/etc –

+0

Con los cálculos' dobles', consulte [Lo que todo científico informático debe saber sobre la aritmética de coma flotante] (http://docs.oracle.com/cd/ E19957-01/806-3568/ncg_goldberg.html). Java resuelve esto al proporcionar opciones de formato para la salida. –

+0

¿Por qué está utilizando 'doubleValue()' ??? Pensé que querías un tipo decimal. –

Respuesta

26

lo que realmente quiere es

new BigDecimal("0.1") 
.add(new BigDecimal("0.1")) 
.add(new BigDecimal("0.1")); 

El constructor new BigDecimal(double) recibe toda la imprecisión de la double, por lo que en el momento en que has dicho 0.1, que ya ha introducido el error de redondeo. El uso del constructor String evita el error de redondeo asociado con ir a través del double.

+0

gracias por todo! Ahora ... Puedo entender eso. ' // FUNCIONA PARA MI PROBLEMA Valor doble = 0.1d; BigDecimal total = new BigDecimal ("0.0"); para (int i = 0; i <9; i ++) { \t total = total.add (nuevo BigDecimal (value.toString())); } System.out.print (total); \t // NO FUNCIONA System.out.print (0.1d + 0.1d + 0.1d); // NO FUNCIONA System.out.println (nuevo BigDecimal (0.1) .add (nuevo BigDecimal (0.1)). Add (nuevo BigDecimal (0.1))); // FUNCIONA System.out.println (nuevo BigDecimal ("0.1"). Add (nuevo BigDecimal ("0.1")). Add (nuevo BigDecimal ("0.1"))); // FUNCIONA PERO CON MENOR PRECISIÓN System.out.println (0.1f + 0.1f + 0.1f); ' – Jefferson

+0

Una ligera corrección: _" imprecisión del doble "_ - esto técnicamente no es cierto. El doble puede representar perfectamente '0.1' - con una mantisa de 1 y un exponente de -1. Es la conversión de un 'doble' a un' BigDecimal' que convierte la representación en coma flotante en una representación binaria normal en algún punto del camino. Esta es la razón por la cual el doble constructor tiene fallas. – theeggman85

+2

Um, ¿qué? No, no puede. '0.1' no se puede representar como una fracción en binario, punto, que es como se representan' double's. Una mantisa de 1 y un exponente de -1 te dan 0.5. –

3

Prueba esto:

BigDecimal sum = new BigDecimal(0.1).add(new BigDecimal(0.1)).add(new BigDecimal(0.1)); 

EDIT: En realidad, mirando sobre el Javadoc, esto tendrá el mismo problema que el original. El constructor BigDecimal(double) hará un BigDecimal correspondiente a la representación exacta de punto flotante de 0.1, que no es exactamente igual a 0.1.

Esto, sin embargo, da el resultado exacto, ya que los números enteros siempre se puede expresar exactamente en representación de coma flotante:

BigDecimal one = new BigDecimal(1); 
BigDecimal oneTenth = one.divide(new BigDecimal(10)); 

BigDecimal sum = oneTenth.add(oneTenth).add(oneTenth); 
+5

NUNCA utilice el constructor 'double' para el decimal grande (bueno, puede haber algunas situaciones raras, pero realmente es una mala idea). Si puede, utilice el constructor de cadenas (que será exacto), si ya tiene un doble uso 'valueOf' de esa manera no obtenemos precisión falsa adicional ... – Voo

3

Esto no es un problema de Java, sino más bien un problema de los ordenadores en general. El problema central radica en la conversión del formato decimal (formato humano) al formato binario (formato de computadora). Algunos números en formato decimal no son representables en formato binario sin infinitos decimales de repetición.

Por ejemplo, 0.3 decimal es 0.01001100 ... binario Pero una computadora tiene "ranuras" (bits) limitadas para guardar un número, por lo que no puede guardar toda la representación infinita. Solo guarda 0.01001100110011001100 (por ejemplo). Pero ese número en decimal ya no es 0.3, sino 0.30000000000000004 en su lugar.

+0

http://en.wikipedia.org/ wiki/Binary-coded_decimal –

8

Primero nunca, nunca use el doble constructor de BigDecimal. Puede ser lo correcto en algunas situaciones, pero principalmente no es

Si puede controlar su entrada, use el constructor BigDecimal String como ya se propuso. De esa forma obtienes exactamente lo que quieres. Si ya tiene un doble (puede suceder después de todo), no use el constructor doble sino el método estático valueOf. Eso tiene la agradable ventaja de que obtenemos la representación canónica del doble que al menos mitiga el problema ... y el resultado suele ser mucho más intuitivo.

2

El problema que tiene es que 0.1 se representa con un número ligeramente mayor, p. Ej.

System.out.println(new BigDecimal(0.1)); 

impresiones

0.1000000000000000055511151231257827021181583404541015625 

El doble.toString() tiene en cuenta este error de representación por lo que no lo ve.

De forma similar, 0.3 se representa con un valor ligeramente más bajo de lo que realmente es.

0.299999999999999988897769753748434595763683319091796875 

Si se multiplica el valor representado de 0,1 por 3 no obtiene el valor representado por 0,3, que en lugar de obtener algo un poco más alto

0.3000000000000000166533453693773481063544750213623046875 

Esto no es sólo un error de representación, sino también un error de redondeo causado por las operaciones. Esto es más de lo que Double.toString() corregirá y entonces verá el error de redondeo.

La moraleja de la historia, si usa float o double también redondea la solución de forma adecuada.

double d = 0.1 + 0.1 + 0.1; 
System.out.println(d); 
double d2 = (long)(d * 1e6 + 0.5)/1e6; // round to 6 decimal places. 
System.out.println(d2); 

impresiones

0.30000000000000004 
0.3 
Cuestiones relacionadas