Question

je débogage mon projet et ne pouvait pas trouver un bug. Enfin je trouve cela. Regardez le code. Vous pensez que tout est OK, et résultat sera « OK! OK! OK! », Non? Maintenant, le compiler avec VC (je l'ai essayé VS2005 et 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 constante magique est à double 90112,0. Lorsque x <90112,0 tout est OK, lorsque x> 90112,0 - Non! Vous pouvez modifier cos au péché.

Toutes les idées? Ne pas oublier que le péché et cos sont périodiques.

Était-ce utile?

La solution

Peut-être ceci: http: //www.parashift .com / c ++ - faq-lite / newbie.html # faq-29,18

  

Je sais qu'il est difficile d'accepter, mais l'arithmétique en virgule flottante ne fonctionne tout simplement pas comme la plupart des gens attendent. Pire encore, certaines des différences dépendent des détails de votre particulier le matériel à virgule flottante de l'ordinateur et / ou les paramètres d'optimisation que vous utilisez sur votre compilateur particulier. Vous ne pourriez pas comme ça, mais c'est la façon dont il est. La seule façon de « l'obtenir » est de mettre de côté vos hypothèses sur la façon dont les choses devriez- pour se comporter et d'accepter les choses comme elles en fait faire ... se comportent

     

(en mettant l'accent sur le mot « souvent », le comportement dépend de votre matériel, compilateur, etc.): calculs à virgule flottante et les comparaisons sont souvent effectuées par le matériel spécial qui contiennent souvent des registres spéciaux, et ces registres ont souvent plus de bits qu'un double. Cela signifie que les calculs à virgule flottante intermédiaires ont souvent plus de bits que sizeof(double), et quand une valeur en virgule flottante est écrit à la RAM, il devient souvent tronquée, perdant souvent quelques bits de précision ...

     

rappelez-vous ceci: comparaisons à virgule flottante sont délicates et subtiles et pleine de dangers. Faites attention. Virgule flottante façon en fait fonctionne est différent de la façon dont la plupart des programmeurs ont tendance à penser que devriez- pour travailler. Si vous avez l'intention d'utiliser à virgule flottante, vous avez besoin d'apprendre comment cela fonctionne en fait ...

Autres conseils

Comme d'autres l'ont noté, la bibliothèque de mathématiques VS fait son calcul sur la FPU x87, et générer des résultats de 80 bits, même si le type est double.

Ainsi:

  1. cos () est appelée, et revient avec cos (x) sur le dessus de la pile de x87 comme un flotteur de 80 bits
  2. cos (x) est relevé de la pile de x87 et stockée dans la mémoire en tant que double; ce qu'il fait à arrondir à flotteur 64bit, qui change sa valeur
  3. cos () est appelée, et revient avec cos (x) sur le dessus de la pile de x87 comme un flotteur de 80 bits
  4. la valeur arrondie est chargé sur la pile de la mémoire x87
  5. les valeurs arrondies et non arrondies de cos (x) comparer inégale.

De nombreuses bibliothèques mathématiques et compilateurs vous protéger contre cela soit fait le calcul du flottant 64 bits dans les registres SSE lorsqu'ils sont disponibles, ou en forçant les valeurs à stocker et arrondi avant la comparaison, ou en stockant et en rechargeant le résultat final le calcul réel de cos (). Le compilateur / combinaison de la bibliothèque vous arrive de travailler avec ne pardonnent pas.

Les cos (x) == cos procédure (x) produit dans le mode de libération:

00DB101A  call        _CIcos (0DB1870h) 
00DB101F  fld         st(0) 
00DB1021  fucompp 

La valeur est calculée une fois, puis cloné, puis par rapport à lui-même - résultat sera ok

Le même en mode débogage:

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          

Maintenant, les choses étranges se produisent.
1. X est chargé sur fstack (X, 0)
2. X est stocké sur la pile normale (troncature)
3. Cosinus est calculée, le résultat sur la pile flottante
4. X est à nouveau chargé
5. X est stocké sur la pile normale (troncature, comme pour l'instant, nous sommes « symétriques »)
6. Le résultat du 1er cosinus qui était sur la pile est stockée en mémoire, maintenant, un autre troncature se produit pour la 1ère valeur
7. Cosinus est calculé, 2e résultat si le flotteur pile, mais cette valeur a été tronquée une seule fois
8. 1ère valeur est chargée sur le fstack, mais cette valeur a été tronquée deux fois (une fois avant cosinus calcul, une fois après)
9. Ces 2 valeurs sont comparées -. Nous obtenons des erreurs d'arrondi

Vous devriez jamais pas comparer double pour l'égalité dans la plupart des cas. Vous ne pouvez pas obtenir ce que vous attendez.

registres à virgule flottante peuvent avoir une taille différente de celle des valeurs de la mémoire (dans les machines actuelles d'Intel, les registres FPU sont 80 bits vs 64 double bit). Si le compilateur est la génération de code qui calcule la première cosinus, puis stocke la valeur en mémoire, calcule la seconde cosinus et compare la valeur dans la mémoire de celui dans le registre, puis les valeurs pourraient être différents (en raison de l'arrondissement des questions de 80 à 64 bits) .

valeurs à virgule flottante sont un peu délicat. Google pour comparissons à virgule flottante.

Le compilateur aurait code généré qui finit par la comparaison d'une double valeur de 64 bits avec un 80 bits registre à virgule flottante interne. Test de valeurs à virgule flottante pour l'égalité est sujette à ces sortes d'erreurs - vous êtes presque toujours mieux faire une comparaison « floue » comme (usines de fabrication (val1 - val2) .

incrémenter et tester une valeur flottante comme une variable de contrôle de boucle est généralement une très mauvaise idée. Créer un int LCV séparée juste pour une boucle, si vous devez.

Dans ce cas, il est plus facile:

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);
}

Comment problème autour? Modifier si bloc:

if ( (float)cos(x) == (float)cos(x) )
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top