web-dev-qa-db-fra.com

le résultat sinusoïdal dépend du compilateur C ++ utilisé

J'utilise les deux compilateurs C++ suivants:

  • cl.exe: Compilateur d'optimisation Microsoft (R) C/C++ Version 19.00.2421 pour x86
  • g ++: g ++ (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010

Lorsque j'utilise 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 importants 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 point ici. Je comprendrais vraiment ce qui se passe ici: pourquoi cl.exe est-il si mal?

Chose drôle, si j'applique un modulo de (2 * pi) sur le param, alors j'obtiens le même résultat que g ++ ...

[EDIT] Tout simplement parce que mon exemple semble fou pour certains d'entre vous: cela fait 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 en avons juste besoin pour donner un résultat.

37
Nicolas

Je pense que le commentaire de Sam est le plus proche de la marque. Alors que vous utilisez une version récente de GCC/glibc, qui implémente sin () dans le logiciel (calculé au moment de la compilation pour le littéral en question), cl.exe pour x86 utilise probablement l'instruction fsin. Ce dernier peut être très imprécis, comme décrit dans l'article de blog Random ASCII, " Intel sous-estime les limites d'erreur de 1,3 quintillion ".

Une partie du problème avec votre exemple en particulier est qu'Intel utilise une approximation imprécise de pi lors de la réduction de la plage:

Lors de la réduction de la plage de double précision (mantisse 53 bits) pi , les résultats auront environ 13 bits de précision (66 moins 53), pour un erreur jusqu'à 2 ^ 40 ULPs (53 moins 13).

16
SloopJon

Vous avez un littéral à 19 chiffres, mais le double a généralement une précision de 15 à 17 chiffres. Par conséquent, vous pouvez obtenir une petite erreur relative (lors de la conversion en double), mais une erreur absolue suffisamment grande (dans le contexte du calcul du sinus).

En fait, différentes implémentations de la bibliothèque standard ont des différences dans le traitement de ces grands nombres. Par exemple, dans mon environnement, si nous exécutons

std::cout << std::fixed << 5451939907183506432.0;

le résultat g ++ serait 5451939907183506432.000000
cl résultat serait 5451939907183506400.000000

La différence est que les versions de cl antérieures à 19 ont un algorithme de formatage qui n'utilise qu'un nombre limité de chiffres et remplit les décimales restantes par zéro.

De plus, regardons ce code:

double a[1000];
for (int i = 0; i < 1000; ++i) {
    a[i] = sin(5451939907183506432.0);
}
double d = sin(5451939907183506432.0);
cout << a[500] << endl;
cout << d << endl; 

Lorsqu'il est exécuté avec mon compilateur x86 VC++, la sortie est:

0.522491
0.528463

Il apparaît que lors du remplissage du tableau sin est compilé à l'appel de __vdecl_sin2, et lorsqu'il y a une seule opération, elle est compilée à l'appel de __libm_sse2_sin_precise (avec /fp:precise).

À mon avis, votre nombre est trop grand pour que le calcul de sin puisse attendre le même comportement de différents compilateurs et s'attendre au comportement correct en général.

36
DAle

Selon cppreference :

Le résultat peut avoir peu ou pas de signification si la magnitude de arg est grande (jusqu'à C++ 11)

Il est possible que ce soit la cause du problème, auquel cas vous souhaiterez faire manuellement le modulo afin que arg ne soit pas grand.

9
SirGuy