2012-01-10 3 views
14

Je suis à la recherche dans why a test case is failingdifférence d'arrondi gcc entre les versions

Le test problématique peut être réduit à faire (4.0/9.0) ** (1.0/2.6), arrondir ce à 6 chiffres et vérification par rapport à une valeur connue (sous forme de chaîne):

#include<stdio.h> 
#include<math.h> 
int main(){ 
    printf("%.06f\n", powf(4.0/9.0, (1.0/2.6))); 
} 

Si je compiler et exécuter ce dans gcc 4.1.2 sur Linux, je reçois:

0.732057 

Python est d'accord, tout comme Wolfram|Alpha:

$ python2.7 -c 'print "%.06f" % (4.0/9.0)**(1/2.6)' 
0.732057 

Cependant, je reçois le résultat suivant sur gcc 4.4.0 sur Linux et 4.2.1 sur Mac OS X:

0.732058 

A double actes identique (bien que je n'ai pas testé ce largement)

Je ne suis pas sûr de savoir comment réduire encore plus. Est-ce une régression gcc? Un changement d'algorithme d'arrondi? Moi qui fais quelque chose de stupide?

Edit: Impression du résultat à 12 chiffres, le chiffre à la 7ème place est 4 vs 5, ce qui explique la différence d'arrondi, mais pas la différence de valeur:

gcc 4.1.2:

0.732057452202 

gcc 4.4.0:

0.732057511806 

est ici la sortie gcc -S des deux versions: https://gist.github.com/1588729

+0

Pour supprimer 'printf()' se comportant différemment en tant que variable, imprimez ou inspectez les bits de la mémoire contenant la valeur, de façon à supprimer la partie "convertir en chaîne" de l'article. – unwind

+3

La différence entre ces deux est exactement la [machine epsilon] (http://en.wikipedia.org/wiki/Machine_epsilon) d'un processeur 32 bits. Le résultat de gcc4.4 est également plus proche de la valeur réelle de l'expression. –

Répondre

8

La version gcc récente est capable d'utiliser mfpr pour effectuer le calcul en virgule flottante à la compilation. Ma conjecture est que votre gcc récent fait cela et utilise une plus grande précision pour la version de compilation. Ceci est permis par au moins la norme C99 (je ne l'ai pas regardé dans une autre si elle a été modifiée)

6.3.1.8/2 dans C99

Les valeurs des opérandes flottantes et des résultats d'expressions flottantes peut être représenté avec une plus grande précision et portée que celle requise par le type; les types ne sont pas changés ainsi.

Éditez: vos résultats de gcc -S le confirment. Je ne l'ai pas vérifié les calculs, mais l'ancien a (après avoir remplacé la mémoire de son contenu constant)

movss 1053092943, %xmm1 
movss 1055100473, %xmm0 
call powf 

appelant powf avec les valeurs précalculées pour 4/9,0 et 1/2,6 et puis imprimer le résultat après la promotion pour doubler, tandis que le nouveau juste imprimer le flotteur 0x3f3b681f promu à doubler.

+0

Intéressant, cela semble être la cause! Pourriez-vous élaborer sur ce qui dans le démontage indique cela? (une chose - l'ancien gcc4.1.2 probablement utilisé mfpr - son résultat correspond à d'autres sources, y compris la réponse de Daniel) – dbr

+0

@dbr, voir mise à jour. Cela répond-il à vos besoins? – AProgrammer

3

Il y a beaucoup de joueurs ici. Gcc va très probablement transférer le calcul à votre processeur à virgule flottante; vous pouvez vérifier le démontage pour cela.

Vous pouvez consulter les résultats binaires avec une représentation binaire (de même wolfram/alpha):

float q=powf(4.0/9.0, (1.0/2.6)); 
unsigned long long hex=*reinterpret_cast<unsigned long long*>(&q); 
unsigned long long reference=0x1f683b3f; 
assert(hex==reference); 

Mais aussi printf est un coupable possible: la représentation décimale de ce nombre peut être le problème, aussi. Vous pouvez essayer d'écrire printf("%0.06f", 0.73205748); pour tester cela.

1

Vous devriez être en mesure de distinguer différemment l'arrondi du format et le résultat mathématique donnant une réponse différente, simplement en imprimant plus (tous) les chiffres significatifs.

Si l'arrondi est identique, printf("%0.6f" arrondit différemment.


OK, avec l'ancien Linux + environnement python Je dois la main, je reçois:

Python 2.4.3 (#1, Jun 11 2009, 14:09:37) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-44)] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> (4.0/9.0)**(1.0/2.6) 
0.7320574847636997 

qui est différent à nouveau.

Peut-être serait-il plus simple de demander à la place, combien de chiffres significatifs sont vraiment significatifs pour ce test unitaire?

+0

Réponse mise à jour avec des valeurs imprimées à 12 chiffres - la valeur à la 7ème place change entre 4 et 5, donc printf arrondit uniformément – dbr

+0

Votre résultat arrondit au résultat gcc 4.1.2, 'rond (0.7320574847636997, 6) == 0.732057 '. L'unittest serait idéalement précis à 6 chiffres car ce nombre de chiffres est stocké dans le format LUT par défaut, mais je verrai si je peux le réduire à 5 – dbr

4

Je pense que l'ancien gcc utilisé double sous le capot. Faire le calcul en Haskell et l'impression des résultats à la précision complète, je reçois

Prelude Text.FShow.RealFloat> FD ((4/9) ** (1/2.6)) 
0.73205748476369969512944635425810702145099639892578125 
Prelude Text.FShow.RealFloat> FF ((4/9) ** (1/2.6)) 
0.732057511806488037109375 

Ainsi, le résultat double est d'accord avec ce que gcc-4.1.2 produit et le résultat float avec ce gcc-4.4.0 fait. Les résultats gcc-4.5.1 produisent ici pour float resp. double sont d'accord avec les résultats de Haskell. Comme un programmeur cité, le compilateur est autorisé à utiliser une plus grande précision, l'ancien gcc fait, le nouveau ne le fait pas.

+0

Cela illustre bien la différence - merci! – dbr

+1

+1 Donc pour le nouveau compilateur c'est une fonctionnalité, pas un bug? –

Questions connexes