2012-03-28 8 views
13

Si tengo una matriz de dobles que tienen EXACTAMENTE dos lugares decimales, agréguelas por completo mediante un bucle e imprima el total; lo que sale es un número con MÁS DE dos lugares decimales. Lo cual es extraño, porque teóricamente, al sumar dos números, cada uno con 2 y solo 2 decimales NUNCA producirá un número que tenga un dígito distinto de cero más allá del lugar de las centésimas.Comportamiento extraño de Java: ¿Cómo es que al agregar dobles con EXACTAMENTE dos lugares decimales resulta en un doble con MÁS DE dos lugares decimales?

Trate de ejecución de este código:

double[] d = new double[2000]; 
for (int i = 0; i < d.length; i++) { 
    d[i] = 9.99; 
} 

double total = 0,00; 
for (int i = 0; i < d.length; i++) { 
    total += d[i]; 
    if (("" + total).matches("[0-9]+\\.[0-9]{3,}")) { // if there are 3 or more decimal places in the total 
     System.out.println("total: " + total + ", " + i); // print the total and the iteration when it occured 
    } 
} 

En mi equipo, esto imprime:

total: 59.940000000000005, 5 

Si redondear el total a dos cifras decimales a continuación, me gustaría obtener el mismo número que Lo haría si manualmente agregué 9.99 seis veces en una calculadora. ¿Pero cómo es que esto está sucediendo y de dónde vienen los decimales extra? ¿Estoy haciendo algo mal o (dudo que esto sea probable) es esto un error de Java?

+0

posible duplicado de [Doble impredecible] (http://stackoverflow.com/questions/9667324/unpredictable-double) – assylias

+0

¿Ha intentado utilizar DecimalFormat y RoundingMode? http://docs.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html http://docs.oracle.com/javase/6/docs/api/java/math/RoundingMode.html – ChadNC

Respuesta

8

¿Está familiarizado con la conversión de la base 10 a la base 2 (de decimal a binario) para las fracciones? Si no, búscalo.

Luego verá que aunque 9.99 se ve bastante normal en la base 10, realmente no se ve muy bien en binario; Parece un decimal que se repite, pero en binario. Estoy seguro de que has visto un decimal que se repite antes, ¿verdad? No termina Pero Java (o cualquier lenguaje para el caso) tiene que guardar esa secuencia infinita de dígitos en un número limitado de bytes. Y es entonces cuando aparecen los dígitos adicionales. Cuando conviertes ese binario truncado en decimal, en realidad estás tratando con un número diferente. El número almacenado en la variable no es 9.99 exactamente, es algo así como 9.9999999991 (solo un ejemplo, no resolví las matemáticas).

Pero probablemente esté interesado en cómo solucionar esto, ¿no? Busque la clase BigDecimal. Eso es lo que desea usar para sus cálculos, especialmente cuando se trata de dinero. Además, busque DecimalFormat, que es una clase para escribir un número como una cadena con el formato correcto. Creo que redondea para ti cuando solo quieres mostrar 2 dígitos decimales y tu número tiene mucho más, por ejemplo.

2

esto se debe a la aritmética de coma flotante.

dobles y flotadores no son exactamente los números reales, hay un número finito de bits para representar a ellos, mientras que hay un número infinito de números reales [en cualquier rango], por lo que no puede representar todos los números reales - que está recibiendo el número más cercano que puede tener con la representación de coma flotante.

Siempre que trabaje con puntos flotantes, recuerde que son solo una aproximación al número que busca. Es posible que desee utilizar BigDecimal si desea el número exacto [o al menos controlar el error].

Más información se puede encontrar en this article

2

Uso BigDecimal para realizar cálculos de coma flotante con precisión. Es imprescindible cuando se trata de dinero.

Este es un problema conocido que tiene su origen en el hecho de que los cálculos binarios no permiten operaciones de coma flotante precisas. Mira "floating point arithmetics" para más detalles.

+0

porque no intenta representar el número en binario. – Bozho

9

Si tengo una serie de dobles que cada uno tiene exactamente dos cifras decimales

Dejemos de ahí, porque sospecho que no. Por ejemplo, le das 9.99 en tu código de muestra. Eso no es realmente 9.99. Ese es "el doble más cercano a 9.99" ya que 9.99 en sí mismo no puede representarse exactamente en el punto flotante binario.

En ese punto, el resto de su razonamiento se va por la ventana.

Si desea que los valores con un número exacto de decimales dígitos, se debe utilizar un tipo que almacena valores en un decimal centradas manera, como BigDecimal. Alternativamente, almacene todo como enteros y "sepa" que en realidad está recordando "el valor * 100".

+3

+1: punto flotante representa un número como una serie de potencias de 2. p. Ej. 1.625 es 2^0 + 2^-1 + 2^-3.Entonces hay cuatro valores con dos decimales 0.00, 0.25, 0.50, 0.75 y estos no tendrán un redondeo (para +, -, * y /) o error de representación. Los otros dos valores decimales son una aproximación. –

1

Esto se debe a las imprecisiones a la hora de representar números decimales utilizando un valor de punto flotante binario.En otras palabras, el doble literal 0.99 en realidad no representa el valor matemático 9.99.

Para revelar exactamente qué número tiene un valor, como 9.99, podría dejar que BigDecimal imprima el valor.

Código para revelar el valor exacto:

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

Salida:

9.9900000000000002131628207280300557613372802734375 

Por cierto, el razonamiento será totalmente adecuado si estuviera tomando sobre posiciones binarias en lugar de cifras decimales , ya que un número con dos lugares binarios puede ser exactamente representado por un valor de punto flotante binario.

4

Los dobles están representados en un formato binario en la computadora(). Esto significa que ciertos números no se pueden representar con precisión, por lo que la computadora usará el número más cercano que se pueda representar.

E.g. 10.5 = 2^3 + 2 + 2^(- 1) = 1.0101 * 2^3 (aquí la mantisa está en binario)
pero 10.1 = 2^3 + 2 + 2^(- 4) +2^(- 5) + (series infinitas aquí) = 1.0100001 ... * 2^3

9.99 es un número con una representación infinita. Por lo tanto, cuando los agrega juntos, la representación finita utilizada por la computadora se utiliza en el cálculo y el resultado estará aún más lejos de la suma matemática que los originales de su representación real. Es por eso que ve más dígitos que se muestran en los números originales.

1

No sé cómo marcar como un duplicado, sino una breve búsqueda me llevan a esta página: How to resolve a Java Rounding Double issue ¿Es esto lo que busca?

+0

No, gracias. No tengo problemas para redondear dobles, todo el asunto binario simplemente no se me ocurrió. Gracias. –

Cuestiones relacionadas