J'utilise les deux compilateurs C++ suivants :
- cl.exe : Compilateur optimisant Microsoft (R) C/C++ Version 19.00.24210 pour x86
- g++ : g++ (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010
En utilisant la fonction sinus intégrée, j'obtiens des résultats différents. Ce n'est pas critique, mais parfois les résultats sont trop significatifs pour mon usage. Voici un exemple avec une valeur "codée en dur" :
printf("%f\n", sin(5451939907183506432.0));
Résultat avec cl.exe :
0.528463
Résultat avec g++ :
0.522491
Je sais que le résultat de g++ est plus précis et que je pourrais utiliser une bibliothèque supplémentaire pour obtenir ce même résultat, mais ce n'est pas mon propos ici. Je voudrais vraiment comprendre ce qui se passe ici : pourquoi cl.exe est-il si mauvais ?
C'est drôle, si j'applique un modulo de (2 * pi) sur le paramètre, alors j'obtiens le même résultat que g++...
[EDIT] Juste parce que mon exemple semble fou pour certains d'entre vous : il s'agit d'une partie d'un générateur de nombres pseudo-aléatoires. Il n'est pas important de savoir si le résultat du sinus est exact ou non : nous avons juste besoin qu'il donne un certain résultat.
14 votes
Un coup d'œil au jeu d'instructions x86 permet de localiser les instructions mathématiques FSIN et FCOS, implémentées dans le matériel, de sorte que l'on peut s'attendre à ce que les résultats soient indépendants du compilateur. Peut-être que l'on sait que FSIN/FCOS est inexact avec de grandes valeurs, et que gcc fait un effort supplémentaire, et applique manuellement le modulo avant d'exécuter FSIN/FCOS.
0 votes
Par curiosité : pourquoi avez-vous besoin d'un argument aussi important ?
0 votes
@Henrik Ce n'est qu'un petit morceau d'algorithme : l'instruction précédente peut générer ce genre de valeurs énormes.
14 votes
Gardez en mémoire que
sin(x+2pi)
est égal àsin(x)
. En pratique, cela signifie que l'argument desin
est mis à l'échelle dans lesin
de manière à ce qu'elle se situe dans l'intervalle [0..2pi]. Plus l'argument est grand, moins les bits de poids faible sont significatifs. L'extraction des bits de poids faible par la mise à l'échelle préalable perd en précision à mesure que l'argument devient plus grand. Essentiellement, lorsque vous essayez de calculer le sin d'un argument aussi grand, le résultat est absurde. La fonction essaie de calculer le sin du bruit. Garbage in, garbage out.19 votes
Quels commutateurs de compilateur passez-vous dans chaque cas ? Les optimisations sont-elles activées ? La précision est contrôlable avec les options. Les instructions x87 FSIN et FCOS sont un hareng rouge ici. Elles ne sont pas utilisées par les compilateurs modernes, ayant été remplacées par les instructions SSE2. Avec GCC, même avec les optimisations désactivées, il calcule ceci à temps de compilation et crache le résultat sous forme de littéral. MSVC ne le fera pas.
2 votes
"ceci est une partie d'un générateur de nombres pseudo-aléatoires" : à titre d'information, à moins que vous n'exploitiez un détail d'implémentation obscur (mais vous ne poseriez pas cette question dans ce cas), l'utilisation de la virgule flottante pour un RNG me semble une mauvaise idée ...
0 votes
Il peut y avoir différents schémas d'arrondi pour vos plateformes. Veuillez vérifier référence
1 votes
@SamVarshavchik, ils le sont en effet. connu pour être inexact mais il n'est pas utilisé couramment de toute façon
9 votes
Utiliser le péché de cette façon pour le générateur aléatoire est une très mauvaise idée. Ne le faites pas.
0 votes
Merci pour tous vos commentaires "ne faites pas ça"... Mais dans ce cas particulier, je n'ai pas le choix.
19 votes
A deux reprises, on m'a demandé : "Je vous en prie, M. Babbage, si vous introduisez dans la machine des chiffres erronés, les bonnes réponses en sortiront-elles ?" ... Je ne suis pas capable d'appréhender correctement le genre de confusion d'idées qui pourrait provoquer une telle question. - Charles Babbage
0 votes
@CodyGray pour être juste, le seul jeu d'instructions qu'il mentionne est x86, et il ne précise pas qu'il ne compile pas en mode 32 bits. Le compilateur n'utilisera les instructions SSE2 que pour x86_64 (où leur existence est garantie) ou si un drapeau spécifique à l'architecture est fourni (par exemple -msse2).
3 votes
On utilise la fonction sinus dans un générateur de nombres aléatoires ? Et vous ne vous souciez même pas de savoir si le résultat est correct ? Son très suspect, comme le type qui pensait que multiplier deux nombres aléatoires rend le résultat "plus aléatoire". .
0 votes
@Nicolas : pouvez-vous nous dire dans quelles circonstances vous n'avez pas le choix ? Je suis vraiment curieux à ce sujet, pourquoi un péché non fiable est une solution ici (même IEEE 754 ne mandate pas qu'un péché doit retourner le résultat le plus exact. Pas seulement pour les grands nombres, mais pour n'importe quel nombre).
0 votes
@geza J'ai vu un botnet avec un algorithme de génération de domaine ("DGA") écrit en assembleur 32-bit i386 pour win32 qui utilisait un RNG basé sur la trigonométrie, et qui dépendait également des mathématiques "rapides" de l'IEEE. Tenter de désosser ce DGA et de reconstruire ce code pour le faire fonctionner sur d'autres systèmes afin que les domaines du DGA puissent être préenregistrés n'était pas une mince affaire.
0 votes
@Alnitak : Je vois. Attendez-vous à ce que ce soit difficile à reproduire à 100% sur d'autres plateformes (surtout si elles ont utilisé i387). Vous pouvez facilement atteindre 99,9999%, mais les 0,0001% restants sont difficiles :
sin
n'est pas normalisé par l'IEEE.2 votes
Ce n'est pas correct, @Steve. Les versions modernes de GCC et MSVC ciblent par défaut SSE2, même pour les constructions 32 bits. Vous devez explicitement désactiver cela avec un commutateur de compilateur qui les force à cibler à nouveau le x87 sans aucun support de jeu d'instructions étendu. En d'autres termes,
-msse2
y/arch:SSE2
(respectivement) sont les commutateurs par défaut/impliqués pour les constructions 32 bits. C'est pourquoi j'ai posé la question des options du compilateur au tout début. Vous verrez toujours les instructions x87 utilisées quand elles sont appropriées, grâce à la convention d'appel, mais FSIN/FCOS ne sont pas utilisés.1 votes
The result may have little or no significance if the magnitude of arg is large (until C++11)
0 votes
@CodyGray Je viens de tester cela avec gcc 5.3.1 (pas trunk, mais assez moderne).
gcc -m32 -dM -E -x c /dev/null | grep SSE
ne donne aucun résultat alors quegcc -dM -E -x c /dev/null | grep SSE
liste toutes les macros sse jusqu'à sse2. Construction d'un programme de test simple avec -m32 (les contenus principaux étaientprintf("%g\n", sin(atof(v[1]) + 1));
) a montré l'utilisation des instructions x87. De plus, gcc a cette ligne dans sa documentation concernant -mfpmath=387 "C'est le choix par défaut pour les cibles x86-32 non Darwin." (En fait, Darwin active par défaut tout jusqu'à SSE4.2).