294 votos

¿Por qué cambiar el orden de suma devuelve un resultado diferente?

¿Por qué el cambio de la suma de la orden devuelve un resultado diferente?

23.53 + 5.88 + 17.64 = 47.05

23.53 + 17.64 + 5.88 = 47.050000000000004

Tanto Java y JavaScript devuelven los mismos resultados.

Entiendo que, debido a la forma de los números de punto flotante se representan en binario, algunos de los números racionales (como 1/3 - 0.333333...) puede ser representado precisamente.

¿Por qué simplemente cambiando el orden de los elementos afecta el resultado?

276voto

Jon Skeet Puntos 692016

Tal vez esta pregunta es estúpida, pero ¿por qué simplemente cambiando el orden de los elementos afecta el resultado?

Se va a cambiar los puntos en los que los valores están redondeados, con base en su magnitud. Como un ejemplo del tipo de cosas que estamos viendo, vamos a suponer que en lugar de binario de punto flotante, se estaba utilizando un decimal tipo de punto flotante con 4 dígitos significativos, donde cada adición se realiza en el "infinito" de precisión y, a continuación, redondeado a la unidad representable número. Aquí hay dos sumas:

1/3 + 2/3 + 2/3 = (0.3333 + 0.6667) + 0.6667
                = 1.000 + 0.6667 (no rounding needed!)
                = 1.667 (where 1.6667 is rounded to 1.667)

2/3 + 2/3 + 1/3 = (0.6667 + 0.6667) + 0.3333
                = 1.333 + 0.3333 (where 1.3334 is rounded to 1.333)
                = 1.666 (where 1.6663 is rounded to 1.666)

No necesitamos ni no-enteros para que esto sea un problema:

10000 + 1 - 10000 = (10000 + 1) - 10000
                  = 10000 - 10000 (where 10001 is rounded to 10000)
                  = 0

10000 - 10000 + 1 = (10000 - 10000) + 1
                  = 0 + 1
                  = 1

Esto demuestra, quizá más claramente que la parte importante es que tenemos un número limitado de dígitos significativos - no es un número limitado de cifras decimales. Si pudiéramos mantener siempre el mismo número de lugares decimales, entonces con la adición y la sustracción al menos, que estaría bien (siempre que los valores no de desbordamiento). El problema es que cuando llegas a números más grandes, más pequeños de la información se pierde - 10001 ser redondeado a 10000 en este caso. (Este es un ejemplo del problema que Eric Lippert, señaló en su respuesta.)

Es importante tener en cuenta que los valores en la primera línea de la derecha son las mismas en todos los casos - por lo tanto, aunque es importante entender que sus números decimales (23.53, 5.88, 17.64) no puede representarse exactamente como double de valores, que es solo un problema debido a los problemas que se muestra arriba.

52voto

rgettman Puntos 74908

Aquí es lo que está pasando en binario. Como sabemos, algunos valores de punto flotante no puede ser exactamente representados en binario, incluso si pueden ser exactamente representados en forma decimal. Estos 3 números son sólo algunos ejemplos de ese hecho.

Con este programa puedo salida de las representaciones hexadecimales de cada número y los resultados de cada adición.

public class Main{
   public static void main(String args[]) {
      double x = 23.53;   // Inexact representation
      double y = 5.88;    // Inexact representation
      double z = 17.64;   // Inexact representation
      double s = 47.05;   // What math tells us the sum should be; still inexact

      printValueAndInHex(x);
      printValueAndInHex(y);
      printValueAndInHex(z);
      printValueAndInHex(s);

      System.out.println("--------");

      double t1 = x + y;
      printValueAndInHex(t1);
      t1 = t1 + z;
      printValueAndInHex(t1);

      System.out.println("--------");

      double t2 = x + z;
      printValueAndInHex(t2);
      t2 = t2 + y;
      printValueAndInHex(t2);
   }

   private static void printValueAndInHex(double d)
   {
      System.out.println(Long.toHexString(Double.doubleToLongBits(d)) + ": " + d);
   }
}

La printValueAndInHex método es simplemente un hex-impresora auxiliar.

El resultado es como sigue:

403787ae147ae148: 23.53
4017851eb851eb85: 5.88
4031a3d70a3d70a4: 17.64
4047866666666666: 47.05
--------
403d68f5c28f5c29: 29.41
4047866666666666: 47.05
--------
404495c28f5c28f6: 41.17
4047866666666667: 47.050000000000004

Los 4 primeros números son x, y, zy s's representaciones hexadecimales. En representación de punto flotante de IEEE, pedazos de 2 a 12 años representan el binario exponente, es decir, la escala del número. (El primer bit es el bit de signo, y el resto de los bits para la mantisa.) El exponente representado es en realidad el número binario menos 1023.

Los exponentes de los 4 primeros números se extraen:

    sign|exponent
403 => 0|100 0000 0011| => 1027 - 1023 = 4
401 => 0|100 0000 0001| => 1025 - 1023 = 2
403 => 0|100 0000 0011| => 1027 - 1023 = 4
404 => 0|100 0000 0100| => 1028 - 1023 = 5

El primer conjunto de adiciones

El segundo número (y) es de menor magnitud. Cuando la suma de estos dos números para obtener x + y, los 2 últimos bits del segundo número (01) se desplazan fuera de rango y no figura en el cálculo.

El segundo, además agrega x + y y z y suma dos números de la misma escala.

El segundo conjunto de adiciones

Aquí, x + z que ocurra primero. Son de la misma escala, pero el rendimiento de un número que está más arriba en la escala de:

404 => 0|100 0000 0100| => 1028 - 1023 = 5

El segundo, además agrega x + z y y, y ahora 3 bits se cayó de y para sumar los números (101). Aquí, debe haber una ronda hacia arriba, porque el resultado es el siguiente número de punto flotante: 4047866666666666 para el primer conjunto de adiciones vs. 4047866666666667 para el segundo conjunto de adiciones. Ese error es lo suficientemente importante como para mostrar en la impresión del total.

En conclusión, tener cuidado al realizar operaciones matemáticas sobre números de IEEE. Algunas representaciones son inexactas, y se vuelven aún más inexacta cuando las escalas son diferentes. Sumar y restar números de escala similar, si puede.

44voto

Eric Lippert Puntos 300275

Jon respuesta es, por supuesto, correcto. En el caso de que el error no es mayor que el error se acumularian haciendo una simple operación de punto flotante. Tienes un escenario donde en un caso de conseguir error de cero y en el otro consigues un pequeño error; que en realidad no es eso interesante de un escenario. Una buena pregunta es: ¿hay escenarios donde se cambia el orden de los cálculos, que va desde un pequeño error a un (relativamente) un enorme error? La respuesta es rotundamente sí.

Considerar, por ejemplo:

x1 = (a - b) + (c - d) + (e - f) + (g - h);

vs

x2 = (a + c + e + g) - (b + d + f + h);

vs

x3 = a - b + c - d + e - f + g - h;

Obviamente en aritmética exacta sería la misma. Es entretenido para tratar de encontrar los valores de a, b, c, d, e, f, g, h, tal que los valores de x1 y x2 y x3 se diferencian por una gran cantidad. Vea si usted puede hacerlo!

10voto

Compass Puntos 2117

Esta realidad abarca mucho más que Java y Javascript, y es probable que afectan a cualquier lenguaje de programación con el uso de flotadores o dobles.

En la memoria, flotando puntos de usar un formato especial a lo largo de las líneas de IEEE 754 (el convertidor proporciona la mejor explicación de lo que puedo).

De todos modos, aquí está el flotador del convertidor.

http://www.h-schmidt.net/FloatConverter/

La cosa sobre el orden de las operaciones es la "finura" de la operación.

Su primera línea de los rendimientos 29.41 de los dos primeros valores, lo que nos da 2^4 como el exponente.

Su segunda línea se obtiene 41.17 que nos da 2^5 como el exponente.

Estamos perdiendo una figura significativa al aumentar el exponente, que es probable que cambie el resultado.

Intente marcando el último bit del extremo derecho de encendido y apagado para 41.17 y se puede ver que algo tan "insignificante" como 1/2^23 del exponente sería suficiente para causar este punto flotante de diferencia.

Edit: Para aquellos de ustedes que se acuerdan de cifras significativas, esto caería bajo esa categoría. 10^4 + 4999 con una figura significativa de la 1 va a ser 10^4. En este caso, la significativa cifra es mucho menor, pero podemos ver los resultados con el .00000000004 adjunto.

9voto

jbx Puntos 4063

Números de punto flotante se representan utilizando el formato IEEE 754, que proporciona un tamaño específico de bits para la mantisa (mantisa). Por desgracia, esto le da un número específico de 'fracciones de bloques de construcción " para jugar, y ciertos valores fraccionarios no se puede representar con precisión.

Lo que está ocurriendo en tu caso es que en el segundo caso, la suma es probablemente que se ejecutan en cierta precisión problema por el orden de las adiciones son evaluados. No he calculado los valores, pero podría ser, por ejemplo, que 23.53 + 17.64 no pueden ser exactamente representados, mientras que 23.53 + 5.88 puede.

Lamentablemente es un problema conocido que acaba de tener que lidiar con.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X