2012-04-29 34 views
7

que tenía un problema cuando yo estaba añadiendo tres valores de punto flotante, y compararlas con 1.¿Es asociativo de suma y multiplicación de coma flotante?

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 salido diferente?

+0

Su código de ejemplo difiere en * conmutatividad *, no * asociatividad *. Una versión que demuestre asociatividad sería '(0.7 + (0.1 + 0.2))' –

+0

@MattMcNabb: + es una operación binaria. Con operandos de coma flotante, es conmutativo pero no asociativo. Por lo tanto, si tiene dos expresiones que producen resultados diferentes, no puede formar una de la otra aplicando solo conmutatividad. – tmyklebu

+0

@tmyklebu OK, entonces esto comprueba la asociatividad si y solo si se sabe que la conmutatividad se cumple. (El estándar de C++ no parece garantizar la conmutatividad). –

Respuesta

10

La suma 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 What Every Computer Scientist Should Know about Floating Point Arithmetic. Da el siguiente ejemplo:

Otra zona 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)

5

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

El compilador 0,7 codificado 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 inexactamente 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".

Considere ahora ".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 suma 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 suma 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 se producen al redondear, ".7 + .2 + .1" devuelve 0x1.fffffffffffffp-1 (muy poco menos de 1), y ".7 + .1 + .2" regresa 0x1.0000000000000p0 (exactamente 1).

1

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

Prueba:

#include<stdio.h> 
#include<time.h> 
#include<stdlib.h> 
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 programa, alrededor del 30% de las veces, (a*b)*c no es igual a a*(b*c).

+3

o 0% del tiempo si 'RAND_MAX <100000'! –

Cuestiones relacionadas