cosf (M_PI_2) ne renvoyant pas zéro

Cela a commencé soudainement ce matin.

Les lignes originales étaient ceci

float angle = (x+90)*(M_PI/180.0); float xx = cosf(angle); float yy = sinf(angle); 

Après avoir placé un point d’arrêt et un curseur en stationnaire, la réponse correcte pour yy est définie sur 1. mais xx n’est PAS zéro.

J’ai essayé avec cosf(M_PI_2); toujours pas de chance .. cela fonctionnait bien jusqu’à hier .. Je n’ai pas changé les réglages du compilateur, etc.

J’utilise la dernière version de Xcode à compter de ce jour

Contrairement à ce que d’autres ont dit, ce n’est pas un problème de coprocesseur x87. XCode utilise SSE pour le calcul en virgule flottante sur Intel par défaut (à l’exception de l’arithmétique long double ).

Le “problème” est le suivant: lorsque vous écrivez cosf(M_PI_2) , vous demandez au compilateur XCode (gcc ou llvm-gcc ou clang) de procéder comme suit:

  1. Recherchez l’extension de M_PI_2 dans . Selon la norme POSIX, il s’agit d’un littéral à double précision convertissant la valeur correctement arrondie de π / 2.
  2. Arrondissez la valeur de double précision convertie en simple précision.
  3. Appelez la fonction de bibliothèque mathématique cosf sur la valeur de précision simple.

Notez que, tout au long de ce processus, vous n’utilisez pas la valeur réelle de π / 2. Vous utilisez plutôt cette valeur arrondie à un nombre à représenter en virgule flottante. Bien que cos (π / 2) soit exactement égal à zéro, vous ne dites pas au compilateur de faire ce calcul. Vous dites au compilateur de faire cos (π / 2 + minuscule), où minuscule est la différence entre la valeur arrondie (float)M_PI_2 et la valeur exacte (non représentable) de π / 2. Si cos est calculé sans aucune erreur, le résultat de cos (π / 2 + minuscule) est approximativement -in. Si elle renvoyait zéro, ce serait une erreur.

edit: extension pas à pas du calcul sur un Mac Intel avec le compilateur XCode actuel:

M_PI_2 est défini pour être

 1.57079632679489661923132169163975144 

mais ce n’est pas réellement un nombre à double précision représentable. Lorsque le compilateur le convertit en une valeur à double précision, il devient exactement

 1.5707963267948965579989817342720925807952880859375 

C’est le nombre double précision le plus proche de π / 2, mais il diffère de la valeur mathématique réelle de π / 2 d’environ 6,12 * 10 ^ (- 17).

L’étape (2) arrondit ce nombre à simple précision, ce qui change la valeur en exactement

 1.57079637050628662109375 

Ce qui correspond approximativement à π / 2 + 4,37 * 10 ^ (- 8). Lorsque nous calculons le cosf de ce nombre, nous obtenons:

 -0.00000004371138828673792886547744274139404296875 

ce qui est très proche de la valeur exacte du cosinus évalué à ce point:

 -0.00000004371139000186241438857289400265215231661... 

En fait, c’est le résultat correctement arrondi ; il n’y a aucune valeur que le calcul aurait pu renvoyer qui serait plus précise. La seule erreur ici est que le calcul que vous avez demandé au compilateur d’effectuer est différent du calcul que vous pensiez lui demander de faire.

La première chose à noter est que vous utilisez float s. Celles-ci sont insortingnsèquement inexactes et, pour la plupart des calculs, ne vous donnent qu’une approximation proche de la réponse mathématiquement correcte. En supposant que x dans votre code ait la valeur 0 , angle aura une approximation proche de π / 2. xx aura donc une approximation de cos (π / 2). Toutefois, il est peu probable que ce soit exactement égal à zéro en raison de problèmes d’approximation et d’arrondi.

Si vous pouviez changer votre code en double s plutôt qu’en float vous obtiendrez probablement plus de précision et une réponse plus proche de zéro. Cependant, s’il est important que votre code produise une valeur de zéro à ce stade, vous devrez repenser la façon dont vous effectuez les calculs.

Si cela ne répond pas à votre problème, donnez-nous plus de détails et nous aurons une autre reflection.

Je soupçonne que la réponse est aussi proche de 0 que de ne pas avoir à s’inquiéter.

Si je répète la même chose, je reçois la réponse “-4.3711388e-008” qui peut également être écrite sous la forme “-0.000000043711388”. Ce qui est sacrément proche de 0. Assez proche pour ne pas craindre d’être à la 8ème décimale.

Edit: Suite à ce que dit LiraLuna, j’ai écrit le morceau suivant de l’assembleur x87 sous visual studio

  float fRes; _asm { fld1 fld1 fadd st, st(1) fldpi fdiv st, st(1) fcos fstp [fRes] } char str[16]; sprintf( str, "%f", fRes ); 

Fondamentalement, ceci utilise l’instruction fcos du x87 pour faire un cosinus de pi / 2. la valeur détenue dans str est “0.000000”

Ce n’est toutefois pas ce que fcos a renvoyé. Il a effectivement retourné 6.1230318e-017. Cela implique que l’erreur se produit à la 17ème décimale et, disons-le franchement, qu’elle est bien moins importante que le code de débogage standard ci-dessus.

Comme SSE3 n’a pas d’instruction de cosinus spécifique, je suppose (bien que je ne puisse pas confirmer sans voir l’assembleur généré) qu’il utilise soit son propre développement en série taylor, soit l’instruction fcos de toute façon. De toute façon, il est peu probable que vous obteniez une meilleure précision que l’erreur survenue à la 17ème décimale, à mon avis.

La seule chose à laquelle je puisse penser est une substitution de macro malicieuse, c’est-à-dire que M_PI_2 n’est plus 1.57079632679489661923.

Essayez d’appeler cosf( 1.57079632679489661923 ) pour le vérifier.

La vraie chose à laquelle vous devriez faire attention est le signe du cosinus. Assurez-vous que c’est la même chose que prévu. Par exemple, si vous travaillez avec des angles compris entre 0 et pi / 2. assurez- vous que ce que vous utilisez comme PI_2 est inférieur à la valeur réelle de pi / 2 !

Et la différence entre 0.000001 et 0.0 est inférieure à ce que vous pensez.

La raison

Ce que vous vivez, c’est le sortingstement célèbre «bogue» – ou plutôt – une fonctionnalité du float du co-processeur mathématique x87 . Les float IEEE ont une gamme étonnante de chiffres, mais à un coût. Ils sacrifient la précession pour la haute gamme.

Cependant, ils ne sont pas inexacts comme vous le pensez (ceci est un semi-mythe généré par la conception de la puce x87 d’Intel, qui utilise en interne une représentation interne de 80 bits pour les floats), mais ils ont une précession bien supérieure, bien qu’un peu plus lente.

Lorsque vous effectuez une comparaison float, x87 met le float en mémoire cache sous la forme d’un float à 80 bits. Lorsqu’il est saturé, il enregistre la représentation 32 bits dans la RAM, ce qui diminue considérablement la précision.

La solution

x87 est vieux, vraiment vieux. Son remplacement est SSE . SSE calcule les flottants 32 bits et les flotteurs 64 bits en natif, ce qui entraîne une perte de précession minimale en mathématiques. Veuillez noter que les problèmes de précession avec les flottants existent toujours, mais printf("%f\n", cosf(M_PI_2)); devrait être zéro. Heck – même la comparaison flottante avec SSE est à nouveau exacte! (contrairement à x87).

Puisque le dernier Xcode est en réalité GCC 4.2.1, utilisez le commutateur de compilation -msse3 -mfpmath=sse et voyez comment obtenir un tour parfait 0.00000 (Remarque: si vous obtenez -0.00000 , ne vous inquiétez pas, tout va bien et rest égal à 0.00000 sous les spécifications IEEE (pour en savoir plus sur cet article de Wikipédia)).

La prise en charge de SSE3 est garantie pour tous les macs Intel (sauf pour les Mac OSx86, utilisez -msse2 si vous souhaitez les prendre en charge).