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.

¿Fue útil?

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 que sizeof(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:

  1. Se llama a cos() y regresa con cos(x) en la parte superior de la pila x87 como un flotante de 80 bits.
  2. 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
  3. Se llama a cos() y regresa con cos(x) en la parte superior de la pila x87 como un flotante de 80 bits.
  4. el valor redondeado se carga en la pila x87 desde la memoria
  5. 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) )
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top