¿La sum y la multiplicación del punto flotante son asociativas?

Tuve un problema cuando añadí tres valores de coma flotante y los comparé con 1.

cout << ((0.7 + 0.2 + 0.1)==1)<<endl; //output is 0 cout << ((0.7 + 0.1 + 0.2)==1)<<endl; //output is 1 

¿Por qué estos valores saldrían diferentes?

La sum del punto flotante no es necesariamente asociativa. Si cambia el orden en que agrega cosas, esto puede cambiar el resultado.

El documento estándar sobre el tema es Lo que todo científico informático debe saber sobre la aritmética de coma flotante . Da el siguiente ejemplo:

Otra área gris se refiere a la interpretación de paréntesis. Debido a los errores de redondeo, las leyes asociativas del álgebra no necesariamente se mantienen para los números de coma flotante. Por ejemplo, la expresión (x + y) + z tiene una respuesta totalmente diferente que x + (y + z) cuando x = 1e30, y = -1e30 yz = 1 (es 1 en el primer caso, 0 en el segundo )

Lo que es probable, con máquinas y software actualmente populares, es:

El comstackdor codificó .7 como 0x1.6666666666666p-1 (este es el número hexadecimal 1.6666666666666 multiplicado por 2 a la potencia de -1), .2 como 0x1.999999999999ap-3 y .1 como 0x1.999999999999ap-4. Cada uno de estos es el número representable en punto flotante más cercano al número decimal que usted escribió.

Observe que cada una de estas constantes de coma flotante hexadecimales tiene exactamente 53 bits en su significado (la parte de “fracción”, a menudo llamada imprecisamente mantisa). El número hexadecimal para el significando tiene un “1” y trece dígitos hexadecimales más (cuatro bits cada uno, 52 en total, 53 incluyendo el “1”), que es lo que establece el estándar IEEE-754, para flotación binaria de 64 bits. números de puntos.

Agreguemos los números para .7 y .2: 0x1.6666666666666p-1 y 0x1.999999999999ap-3. Primero, escala el exponente del segundo número para que coincida con el primero. Para hacer esto, multiplicaremos el exponente por 4 (cambiando “p-3” por “p-1”) y multiplicaremos el significado por 1/4, dando 0x0.66666666666668p-1. A continuación, agregue 0x1.6666666666666p-1 y 0x0.66666666666668p-1, dando 0x1.ccccccccccccc8p-1. Tenga en cuenta que este número tiene más de 53 bits en el significado: el “8” es el 14º dígito después del período. El punto flotante no puede devolver un resultado con tantos bits, por lo que debe redondearse al número representable más cercano. En este caso, hay dos números que están igualmente cerca, 0x1.cccccccccccccp-1 y 0x1.ccccccccccccdp-1. Cuando hay un empate, se usa el número con un cero en el bit más bajo del significado. “c” es par y “d” es impar, entonces se usa “c”. El resultado final de la adición es 0x1.cccccccccccccp-1.

A continuación, agregue el número de .1 (0x1.999999999999ap-4) a eso. De nuevo, escalamos para que los exponentes coincidan, por lo que 0x1,9999999999999ap-4 pasa a ser 0x.33333333333334p-1. A continuación, agregue eso a 0x1.cccccccccccccp-1, dando 0x1.fffffffffffff4p-1. Redondear eso a 53 bits da 0x1.fffffffffffffp-1, y ese es el resultado final de “.7 + .2 + .1”.

Ahora considere “.7 + .1 + .2”. Para “.7 + .1”, agregue 0x1.6666666666666p-1 y 0x1.999999999999ap-4. Recuerde que este último tiene una escala de 0x.33333333333334p-1. Entonces, la sum exacta es 0x1.99999999999994p-1. Redondeando eso a 53 bits da 0x1.9999999999999p-1.

A continuación, agregue el número de .2 (0x1.999999999999ap-3), que se escala a 0x0.66666666666668p-1. La sum exacta es 0x2.00000000000008p-1. Los significados de coma flotante siempre se escalan para comenzar con 1 (excepto en casos especiales: cero, infinito y números muy pequeños en la parte inferior del rango representable), por lo que ajustamos esto a 0x1.00000000000004p0. Finalmente, redondeamos a 53 bits, dando 0x1.0000000000000p0.

Por lo tanto, debido a los errores que ocurren al redondear, “.7 + .2 + .1” devuelve 0x1.fffffffffffffp-1 (muy poco menos de 1), y “.7 + .1 + .2” devuelve 0x1.0000000000000p0 ( exactamente 1).

La multiplicación de punto flotante no es asociativa en C o C ++.

Prueba:

 #include #include #include using namespace std; int main() { int counter = 0; srand(time(NULL)); while(counter++ < 10){ float a = rand() / 100000; float b = rand() / 100000; float c = rand() / 100000; if (a*(b*c) != (a*b)*c){ printf("Not equal\n"); } } printf("DONE"); return 0; } 

En este progtwig, aproximadamente el 30% del tiempo, (a*b)*c no es igual a a*(b*c) .