2009-08-26 29 views
8

Solo puedo suponer que es un error. La primera afirmación pasa mientras que la segunda falla:gcc error de precisión?

double sum_1 = 4.0 + 6.3; 
assert(sum_1 == 4.0 + 6.3); 

double t1 = 4.0, t2 = 6.3; 

double sum_2 = t1 + t2; 
assert(sum_2 == t1 + t2); 

Si no es un error, ¿por qué?

+0

Puede consultar también: - [http://stackoverflow.com/questions/21265/comparing-ieee-floats-and-doubles-for-equality](http://stackoverflow.com/questions/21265/ comparing-ieee-floats-and-doubles-for-equality) - [http://stackoverflow.com/questions/17333/most-effective-way-for-float-and-double-comparison](http://stackoverflow .com/questions/17333/most-effective-way-for-float-and-double-comparison) - [http://stackoverflow.com/questions/713763/strange-results-with-floating-point-comparison] (http://stackoverflow.com/questions/713763/strange-results-with-floating-point-comparison) – pingw33n

Respuesta

12

Esto es algo que también me ha picado.

Sí, los números en coma flotante nunca se deben comparar para la igualdad debido a un error de redondeo, y probablemente ya lo sabía.

Pero en este caso, está calculando t1+t2, y luego lo vuelve a calcular. Seguramente que tiene que producir un resultado idéntico?

Esto es lo que está pasando. Apuesto a que estás ejecutando esto en una CPU x86, ¿correcto? La FPU x86 usa 80 bits para sus registros internos, pero los valores en la memoria se almacenan como dobles de 64 bits.

Por lo tanto, t1+t2 se calcula primero con 80 bits de precisión, entonces - supongo - almacenado en la memoria en sum_2 con 64 bits de precisión - y se produce un redondeo. Para la afirmación, se carga de nuevo en un registro de coma flotante y t1+t2 se calcula nuevamente, nuevamente con 80 bits de precisión. Así que ahora está comparando sum_2, que previamente se redondeó a un valor de coma flotante de 64 bits, con t1+t2, que se calculó con mayor precisión (80 bits), y es por eso que los valores no son exactamente idénticos.

Editar Entonces, ¿por qué pasa la primera prueba? En este caso, el compilador probablemente evalúe 4.0+6.3 en tiempo de compilación y lo almacene como una cantidad de 64 bits, tanto para la asignación como para la afirmación.Entonces se están comparando valores idénticos, y la afirmación pasa.

Segunda Edición Este es el código ensamblador generado para la segunda parte del código (gcc, 86), con comentarios - más o menos sigue el escenario descrito anteriormente:

// t1 = 4.0 
fldl LC3 
fstpl -16(%ebp) 

// t2 = 6.3 
fldl LC4 
fstpl -24(%ebp) 

// sum_2 = t1+t2 
fldl -16(%ebp) 
faddl -24(%ebp) 
fstpl -32(%ebp) 

// Compute t1+t2 again 
fldl -16(%ebp) 
faddl -24(%ebp) 

// Load sum_2 from memory and compare 
fldl -32(%ebp) 
fxch %st(1) 
fucompp 

nota interesante: Este fue compilado sin optimización Cuando se compila con -O3, el compilador optimiza todos del código de distancia.

+0

No creo que sea así. El problema es que '4.0 + 6.3' es una expresión que el compilador dobla constantemente a 10.3. Entonces, la primera afirmación se vuelve equivalente a 'assert (10.3 == 10.3)' que pasa trivialmente. En la segunda prueba, realmente toma 4.0, lo pone en un doble, toma 6.3, lo pone en un doble (perdiendo un poquito de precisión), los suma, y ​​compara * that * con la constante 10.3, que falla porque es diferente por 2^-70 más o menos. :) – hobbs

+0

No estoy convencido ... si el compilador puede optimizar t1 + t2 a 10.3 en la declaración assert, ¿por qué no puede hacer la misma optimización en la asignación a sum_2? –

+0

Mirando el listado, me tienes a mí. :) Poca conjetura de mi parte, supongo. – hobbs

13

Estás viendo números en coma flotante. No hagas eso, los números de punto flotante tienen un error de precisión inherente en algunas circunstancias. En su lugar, tome el valor absoluto de la diferencia de los dos valores y afirme que el valor es menor que un número pequeño (épsilon).

void CompareFloats(double d1, double d2, double epsilon) 
{ 
    assert(abs(d1 - d2) < epsilon); 
} 

Esto no tiene nada que ver con el compilador y todo que ver con la forma en que se implementan los números de punto flotante. aquí es la especificación IEEE:

http://www.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF

+4

Lo que Ed llama error de precisión es una característica de la aritmética de coma flotante. No hay forma de evitarlo, no es un error, no es una implementación descuidada por parte de los escritores de gcc, es la forma en que las computadoras hacen aritmética en fracciones. Busca "Lo que todo científico informático debe saber sobre la aritmética de coma flotante" y aprende sus lecciones con todo el corazón. –

+0

Sí, agregué un enlace a la especificación. –

+1

Google tiene este "error" también: http://www.google.com/search?hl=es&source=hp&q=999999999999999+-+999999999999998&btnG=Google+Search&cts=1251328513752&aq=f&oq=&aqi= – llamaoo7

1

Las comparaciones de números de doble precisión son inherentemente impreciso. Por ejemplo, a menudo puede encontrar 0.0 == 0.0 que devuelve falso. Esto se debe a la forma en que la FPU almacena y rastrea los números.

Wikipedia says:

Las pruebas para la igualdad es problemático. Dos secuencias computacionales que son matemáticamente iguales pueden producir diferentes valores de coma flotante.

Deberá usar un delta para dar una tolerancia a sus comparaciones, en lugar de un valor exacto.

2

Puede ser que en uno de los casos, termine comparando un doble 64 bits con un registro interno de 80 bits. Puede ser esclarecedor ver las instrucciones de ensamblaje que GCC emite para los dos casos ...

3

He duplicado su problema en mi Intel Core 2 Duo, y miré el código de ensamblaje. Esto es lo que está pasando: cuando su compilador evalúa t1 + t2, lo hace

load t1 into an 80-bit register 
load t2 into an 80-bit register 
compute the 80-bit sum 

Cuando se almacena en sum_2 lo hace

round the 80-bit sum to a 64-bit number and store it 

A continuación, la comparación == compara la suma de 80 bits a una suma de 64 bits, y son diferentes, principalmente porque la parte fraccionaria 0.3 no se puede representar exactamente usando un número de punto flotante binario, por lo que está comparando un 'decimal repetitivo' (en realidad, binario repetitivo) que se ha truncado en dos longitudes diferentes.

Lo que es realmente irritante es que si compila con gcc -O1 o gcc -O2, gcc realiza la aritmética incorrecta en tiempo de compilación y el problema desaparece. Tal vez esto esté bien según el estándar, pero es solo una razón más por la que gcc no es mi compilador favorito.


P.S. Cuando digo que == compara una suma de 80 bits con una suma de 64 bits, por supuesto que realmente quiero decir que compara la versión extendida de la suma de 64 bits. Se podría hacer bien en pensar

sum_2 == t1 + t2 

resuelve a

extend(sum_2) == extend(t1) + extend(t2) 

y

sum_2 = t1 + t2 

resuelve a

sum_2 = round(extend(t1) + extend(t2)) 

Bienvenido al maravilloso mundo de punto flotante!

3

Al comparar los números de punto flotante de cercanía normalmente se quiere medir su diferencia relativa, que se define como

if (abs(x) != 0 || abs(y) != 0) 
    rel_diff (x, y) = abs((x - y)/max(abs(x),abs(y)) 
else 
    rel_diff(x,y) = max(abs(x),abs(y)) 

Por ejemplo,

rel_diff(1.12345, 1.12367) = 0.000195787019 
rel_diff(112345.0, 112367.0) = 0.000195787019 
rel_diff(112345E100, 112367E100) = 0.000195787019 

La idea es medir el número de líderes dígitos significativos que los números tienen en común; si toma el -log10 de 0.000195787019 obtiene 3.70821611, que es aproximadamente el número de 10 dígitos de la base principal que todos los ejemplos tienen en común.

Si es necesario determinar si dos números de coma flotante son iguales que debe hacer algo como

if (rel_diff(x,y) < error_factor * machine_epsilon()) then 
    print "equal\n"; 

donde la máquina epsilon es el número más pequeño que se puede mantener en la mantisa del hardware de punto flotante que se utiliza. La mayoría de los lenguajes de programación tienen una llamada de función para obtener este valor. error_factor debe basarse en el número de dígitos significativos que usted cree que serán consumidos por los errores de redondeo (y otros) en los cálculos de los números xey. Por ejemplo, si sabía que xey eran el resultado de aproximadamente 1000 sumas y no sabía ningún límite en los números que se suman, establecería error_factor en aproximadamente 100.

Intenté agregar estos como enlaces pero no pude ' t ya que este es mi primer post:

  • en.wikipedia.org/wiki/Relative_difference
  • en.wikipedia.org/wiki/Machine_epsilon
  • en.wikipedia.org/wiki/Significand (mantisa)
  • es.wikipedia.org/wiki/Rounding_error
Cuestiones relacionadas