Error de Visual C++ math.h
-
20-09-2019 - |
Pregunta
Estaba depurando mi proyecto y no pude encontrar ningún error.Finalmente lo localicé.Mira el código.Crees que todo está bien y el resultado será "¡OK!¡DE ACUERDO!¡Está bien!", ¿no?Ahora compílelo con VC (he probado vs2005 y vs2008).
#include <math.h>
#include <stdio.h>
int main () {
for ( double x = 90100.0; x<90120.0; x+=1 )
{
if ( cos(x) == cos(x) )
printf ("x==%f OK!\n", x);
else
printf ("x==%f FAIL!\n", x);
}
getchar();
return 0;
}
La doble constante mágica es 90112,0.Cuando x < 90112.0 todo está bien, cuando x > 90112.0 -- ¡No!Puedes cambiar cos por pecado.
¿Algunas ideas?No olvides que el pecado y el cos son periódicos.
Solución
Podría ser esto: http: //www.parashift .com / C ++ - faq-lite / newbie.html # faq-29.18
Sé que es difícil de aceptar, pero la aritmética de punto flotante simplemente no funciona como la mayoría de la gente espera. Peor aún, algunas de las diferencias dependen de los detalles de hardware de su computadora en particular de punto flotante y / o los ajustes de optimización que utiliza en su compilador particular. Usted no le gusta eso, pero es la forma en que está. La única manera de "hacer las cosas" es dejar a un lado sus suposiciones acerca de cómo las cosas debéis a comportarse y aceptar las cosas como son en realidad hacer se comportan ...
(con énfasis en la palabra "a menudo", el comportamiento depende de su hardware, compilador, etc.): cálculos de punto flotante y las comparaciones se realizan a menudo por un hardware especial que a menudo contienen registros especiales, y esos registros a menudo tienen más bits que un
double
. Eso significa que los cálculos de punto flotante intermedios suelen tener más bits quesizeof(double)
, y cuando un valor de punto flotante está escrito en la memoria RAM, que a menudo se trunca, a menudo la pérdida de algunos bits de precisión ...sólo recuerda esto: las comparaciones en coma flotante son difíciles y sutil y lleno de peligros. Ten cuidado. La forma de coma flotante realmente obras es diferente de la forma en la mayoría de los programadores tienden a pensar que debéis a trabajar. Si se va a utilizar en coma flotante, que necesita aprender cómo funciona realmente ...
Otros consejos
Como otros han señalado, la biblioteca matemática VS realiza sus cálculos en la FPU x87 y genera resultados de 80 bits aunque el tipo sea doble.
De este modo:
- Se llama a cos() y regresa con cos(x) en la parte superior de la pila x87 como un flotante de 80 bits.
- cos(x) se extrae de la pila x87 y se almacena en la memoria como un doble;esto hace que se redondee a 64 bits flotantes, lo que cambia su valor
- Se llama a cos() y regresa con cos(x) en la parte superior de la pila x87 como un flotante de 80 bits.
- el valor redondeado se carga en la pila x87 desde la memoria
- los valores redondeados y no redondeados de cos(x) no son iguales.
Muchas bibliotecas y compiladores matemáticos lo protegen de esto ya sea realizando el cálculo en flotante de 64 bits en los registros SSE cuando estén disponibles, o forzando que los valores se almacenen y redondeen antes de la comparación, o almacenando y recargando el resultado final en el cálculo real. de Cos( ).La combinación de compilador/biblioteca con la que estás trabajando no es tan indulgente.
Los cos (x) == cos (x) procedimiento generado en modo de lanzamiento:
00DB101A call _CIcos (0DB1870h) 00DB101F fld st(0) 00DB1021 fucompp
El valor se calcula una vez, y luego clonado, a continuación, en comparación con el mismo - resultado será bien
Lo mismo en modo de depuración:
00A51405 sub esp,8 00A51408 fld qword ptr [x] 00A5140B fstp qword ptr [esp] 00A5140E call @ILT+270(_cos) (0A51113h) 00A51413 fld qword ptr [x] 00A51416 fstp qword ptr [esp] 00A51419 fstp qword ptr [ebp-0D8h] 00A5141F call @ILT+270(_cos) (0A51113h) 00A51424 add esp,8 00A51427 fld qword ptr [ebp-0D8h] 00A5142D fucompp
Ahora, suceden cosas extrañas.
1. X se carga en fstack (X, 0)
2. X se almacena en la pila normal (truncamiento)
3. coseno se calcula, el resultado en el flotador pila
4. X se carga de nuevo
5. X se almacena en la pila normal (truncamiento, como por ahora, somos "simétrica")
6. El resultado de la primera coseno que estaba en la pila se almacena en la memoria, ahora, otro truncamiento se produce por el valor primero
7. coseno se calcula, segundo resultado si en el flotador-pila, pero se truncó este valor sólo una vez
8. primero valor se carga en la fstack, pero este valor se truncó dos veces (una vez antes de calcular coseno, una vez después de)
9. Esos valores se comparan 2 -. Estamos recibiendo errores de redondeo
debe no No compare dobles para la igualdad en la mayoría de los casos. El usuario no puede obtener lo que espera.
registros de coma flotante pueden tener un tamaño diferente de valores de la memoria (en máquinas Intel actuales, registros FPU son 80 bits vs 64 bits duplica). Si el compilador está generando código que calcula la primera coseno, a continuación, almacena el valor en la memoria, calcula el segundo coseno y compara el valor en la memoria de que en el registro a continuación, los valores pueden diferir (debido al redondeo cuestiones de 80 a 64 bits) .
valores de coma flotante son un poco complicado. Google para comparissons punto flotante.
El compilador podría haber generado código que termina la comparación de un valor doble de 64 bits con una
80-bit de registro de punto flotante interno. Prueba de valores de coma flotante para la igualdad
es propenso a este tipo de errores - que está casi siempre mejor hacer una comparación "fuzzy" como (FAB (val1 - val2)
Incremento y la prueba de un valor flotante como variable de control del bucle es generalmente una idea realmente mala. Crear un LCV int separada sólo para el bucle, si es necesario.
En este caso es más fácil:
for ( int i = 90100; i<90120; i+=1 ) {
if ( cos(i) == cos(i) )
printf ("i==%d OK!\n", i);
else
printf ("i==%d FAIL!\n", i);
}
Como en todo problema? Modificar si bloque:
if ( (float)cos(x) == (float)cos(x) )