Bug arithmétique avec g++

Bug arithmétique avec g++ - C++ - Programmation

Marsh Posté le 12-03-2007 à 09:48:20    

Voila un code tout con [:pingouino]

 
Code :
  1. #include <iostream>
  2. using namespace std;
  3. inline double bmass( double s, int b ) { return b/s; }
  4. int main()
  5. {
  6.   printf("%0.36f\n",bmass(0.2,1));
  7.   printf("%d\n",size_t(bmass(0.2,1)));
  8.  
  9.   return 0;
  10. }
 

Et voila le drame :

 

Sous MAC OS X


g++ 3.3 Apple variant :
Reading specs from /usr/libexec/gcc/darwin/ppc/3.3/specs
Thread model: posix
gcc version 3.3 20030304 (Apple Computer, Inc. build 1666)

 


5.000000000000000000000000000000000000
5

 


Sous Win XP avec Mingw


Reading specs from c:/mingw/bin/../lib/gcc/mingw32/3.4.2/specs
Configured with: ../gcc/configure --with-gcc --with-gnu-ld --with-gnu-as --host=
mingw32 --target=mingw32 --prefix=/mingw --enable-threads --disable-nls --enable
-languages=c,c++,f77,ada,objc,java --disable-win32-registry --disable-shared --e
nable-sjlj-exceptions --enable-libgcj --disable-java-awt --without-x --enable-ja
va-gc=boehm --disable-libgcj-debug --enable-interpreter --enable-hash-synchroniz
ation --enable-libstdcxx-debug
Thread model: win32
gcc version 3.4.2 (mingw-special)

 


5.000000000000000000000000000000000000
4

 

Sous Ubuntu 4.xxxx


Using built-in specs.
Target: i486-linux-gnu
Configured
with: ../src/configure -v --enable-languages=c,c++,fortran,objc,obj-c++,treelang --prefix=/usr --enable-shared --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --enable-nls --program-suffix=-4.1 --enable-__cxa_atexit --enable-clocale=gnu --enable-libstdcxx-debug --enable-mpfr --enable-checking=release
i486-linux-gnu
Thread model: posix
gcc version 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5)

 


5.000000000000000000000000000000000000
4

 

[:everything4free]  quid ???

Message cité 1 fois
Message édité par Joel F le 12-03-2007 à 09:50:00
Reply

Marsh Posté le 12-03-2007 à 09:48:20   

Reply

Marsh Posté le 12-03-2007 à 10:03:12    

Joel F a écrit :



(Apple Computer, Inc. build 1666)




stun outil du démon  :o
 
edit : cela dit, ca ressemble simplement à l'implémentation mac qui arrondit plutôt que de tronquer, non ?


Message édité par theshockwave le 12-03-2007 à 10:04:25
Reply

Marsh Posté le 12-03-2007 à 10:08:06    

euh ...
 
size_t(5.00000) je vois pas COMMENT ca peut renvoyer 4 !
Le bug est là ;)

Reply

Marsh Posté le 12-03-2007 à 10:16:39    

parce que c'est pas 5.0
 
 
Edit : je te fais même l'exemple au passage
tu reconnaitras ton code à peine modifié :

Code :
  1. #include <iostream>
  2. using namespace std;
  3. inline double bmass( double s, int b ) { return b/s; }
  4. int main()
  5. {
  6.     printf("%0.36f\n",bmass(0.2,1));
  7.     printf("%d\n",size_t(bmass(0.2,1)));
  8.     printf("%d\n",size_t(5.0));
  9.     return 0;
  10. }


 

$ g++ ctest.cpp -o ctest.exe
ctest.cpp:12:2: warning: no newline at end of file
 
$ ctest.exe
5.000000000000000000000000000000000000
4
5


 

$ g++ --version
g++ (GCC) 3.4.4 (cygming special) (gdc 0.12, using dmd 0.125)
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


 
re-edit:
en fait, mon test ne prouve rien, c'est le matin, tout ca  :sweat: le size_t(5.0) sera probablement converti en 5 dans un size_t ... désolé ...


Message édité par theshockwave le 12-03-2007 à 10:22:01
Reply

Marsh Posté le 12-03-2007 à 12:08:30    

et en assembleur ça donne quoi ?

Reply

Marsh Posté le 12-03-2007 à 15:28:37    

sur ma debian en g++ 4.1.2 ça marche, ça fait un joli li ?, 5

Reply

Marsh Posté le 12-03-2007 à 16:32:12    

bah apparement selon qu'il y est -O4 ou pas ou en fait si, ca change [:pingouino]

Reply

Marsh Posté le 12-03-2007 à 16:35:30    

-O4 ? extension pourrave d'apple ?

Reply

Marsh Posté le 12-03-2007 à 16:37:23    

nana, l'extension pourrave de gcc 4.2 sur machine SSE3 :o
Le fait est que le comportement est correct si y a des optims ou pas.

 

Drame encore + gros :

 
Code :
  1. #include <iostream>
  2. using namespace std;
  3. inline double id( const double & d ) { return d; }
  4. inline double bmass( double s, int b ) { return b/s; }
  5. int main()
  6. {
  7.     printf("%0.36f\n",bmass(0.2,1));
  8.     printf("%d\n",size_t(id(bmass(0.2,1))));
  9.     return 0;
  10. }
 

renvoie 5 dans toutes les configs machines/compilos


Message édité par Joel F le 13-03-2007 à 17:15:26
Reply

Marsh Posté le 13-03-2007 à 00:29:29    

Joel F a écrit :

size_t(5.00000) je vois pas COMMENT ca peut renvoyer 4 !


Dixit §4.9.1: "An rvalue of a floating point type can be converted to an rvalue of an integer type.
The conversion truncates; that is, the fractional part is discarded. [...]"
 

Joel F a écrit :

Le bug est là ;)


Je ne crois pas. la valeur "a peu près 5.000" peut être bien différente suivant les optimisations appliquées. Il n'y a rien dans le standard C++ fasse pour empêcher cela.

Reply

Marsh Posté le 13-03-2007 à 00:29:29   

Reply

Marsh Posté le 13-03-2007 à 08:00:21    

Pardon mais 0.2 n'a pas de représentation exacte (0x3FC999999999999A). Rien à voir avec les optimisations, flags de compil et autres petits hommes verts.
 
La révision des fondamentaux c'est par ici: http://docs.sun.com/source/806-3568/ncg_goldberg.html

Reply

Marsh Posté le 13-03-2007 à 08:29:17    

++fab a écrit :


Je ne crois pas. la valeur "a peu près 5.000" peut être bien différente suivant les optimisations appliquées. Il n'y a rien dans le standard C++ fasse pour empêcher cela.


 
diantre, j'aimerais bien comprendre l'effet de l'ajout d'une etape intermediaire qui ne modifie pas la valeur et qui pourtant en change el résultat final :|
 
 

Citation :


Pardon mais 0.2 n'a pas de représentation exacte (0x3FC999999999999A). Rien à voir avec les optimisations, flags de compil et autres petits hommes verts.
 
La révision des fondamentaux c'est par ici: http://docs.sun.com/source/806-3568/ncg_goldberg.html  


 
Merci bien [:itm] je ne le savais pas :o  
Ma question portait surtout sur l'expliation de la variabilité du comportement d'une même famille de compilateur masi merci pour ce bashage
inapproprié :o

Reply

Marsh Posté le 13-03-2007 à 08:53:02    

Joel F a écrit :

Merci bien [:itm] je ne le savais pas :o  
Ma question portait surtout sur l'expliation de la variabilité du comportement d'une même famille de compilateur masi merci pour ce bashage
inapproprié :o


En quoi est-ce inapproprié? Il n'y a rien de mieux à attendre de la part du compilateur. De n'importe quel compilateur. Croire qu'il en serait autrement est une grossière méprise.

Reply

Marsh Posté le 13-03-2007 à 08:56:32    

tbp a écrit :

En quoi est-ce inapproprié? Il n'y a rien de mieux à attendre de la part du compilateur. De n'importe quel compilateur. Croire qu'il en serait autrement est une grossière méprise.


 
Bon, on va dire qu'il est 8:50 un mardi matin et que je m'exprime mal ... je cherche la cause de la variabilité du phénoméne.
Je me doutes bien qu'on n'y peut rien hein -- genre les cours sur le codage IEEE 754 j'en donne moi même -- néanmoins, force  
est de constater que :
 
- la version du gcc influe sur le résultat
- les options d'optimisations ou leurs absences aussi
- le code adjacent aussi ...
 
Donc ton histoire de "les optims n'ont rien à y voir", permets moi d'en douter ...
Alors, je vais poser une question claire et précise : Pourquoi cette variation ?

Reply

Marsh Posté le 13-03-2007 à 09:43:58    

La question n'a pas à être posée dans ces termes, vu les garanties fournie par le langage et IEEE 754.  
Alors si le fond du problème est de savoir "dans quelles conditions puis-je espérer un résultat 'déterministe' dans une séquence d'opérations sur des flottants en c++?", un nombre sans représentation exacte a peu de chance de faire partie de la réponse (sauf à inclure un pèlerinage à Lourdes).

Reply

Marsh Posté le 13-03-2007 à 11:47:50    

IEEE 754 n'est pas censée répondre à ce problème de déterminisme ? Ne serait-il pas plus approprié de dire: telle optim ou tel jeu d'instruction, n'est pas IEEE 754 compliant ?

Reply

Marsh Posté le 13-03-2007 à 12:58:48    

Je croyais qu'il était question de C++, un modèle un poil plus laxiste qu'IEEE 754.

Reply

Marsh Posté le 13-03-2007 à 16:54:06    

tu veux toujours pas balancer l'assembleur voir un coup ?

Reply

Marsh Posté le 13-03-2007 à 17:26:58    

On repart de 0:
 
1/ je fais un calcul sur des flottants sans representation exacte
2/ ce calcul n'a pas les mêmes résultats selon le compilo/plateforme/options de compil
 
Question : Pourquoi cette variabilité ...

Reply

Marsh Posté le 13-03-2007 à 18:53:15    

Parce que ce bout de code est un bug ambulant.
Au même titre que

Code :
  1. float val = 0;
  2. while (val != 1)
  3. val += .1f;


 
Au mieux le compilo en arrive à  

Code :
  1. movl   $0x443008,(%esp)
  2. $0x5,%eax
  3. mov    %eax,0x4(%esp)
  4. call   410320 <_printf>


au pire, il y a qque transitions [x87/SSE] division [SSE/x87] troncation [SSE/x87] etc...
 
Le problème n'a rien a voir avec le compilateur ou la plateforme, mais avec le fait que l'auteur pense que le résultat va être 5.


Message édité par tbp le 13-03-2007 à 18:55:05
Reply

Marsh Posté le 13-03-2007 à 19:09:29    

bon, tu sais lire ? ... j'ai BIEN COMPRIS que le résultat doit être 4 ...
Malheureusement, ce n'est pas le résultat obtenu SUR TOUTES LE SPLATEFORMES A MA DISPOSITION :o [:fou]

Reply

Marsh Posté le 13-03-2007 à 19:30:03    

J'ai bien compris que tu n'as pas compris.

Reply

Marsh Posté le 13-03-2007 à 19:36:03    

merci [:itm]

Reply

Marsh Posté le 13-03-2007 à 19:52:06    

Joel F a écrit :

bon, tu sais lire ? ... j'ai BIEN COMPRIS que le résultat doit être 4 ...
Malheureusement, ce n'est pas le résultat obtenu SUR TOUTES LE SPLATEFORMES A MA DISPOSITION :o [:fou]


Le monsieur te dit que c'est l'operation que tu fais faire au compilo qui est problematique, et que ton code est donc faux ... Si je fais des depassements de tableaux, je ne me plains pas comme quoi ca ne donne pas les memes resultats sur toutes les plateformes! Ici, c'est pareil.

Reply

Marsh Posté le 13-03-2007 à 19:59:12    

Joel F a écrit :

On repart de 0:

 

1/ je fais un calcul sur des flottants sans representation exacte
2/ ce calcul n'a pas les mêmes résultats selon le compilo/plateforme/options de compil

 

Question : Pourquoi cette variabilité ...


Parce que la norme laisse volontairement dans le flou le cast float ou double --> int (et a fortiori avec size_t), histoire de bénéficier d'optims d'implémentation dans le cast statique.

 

Donc ce que te dit tbp, c'est qu'on ne doit jamais faire ça avec des nombres, mais tjrs utiliser les fonctions trunc/ceil/floor de <math> qui, s'ils sont correctement codés, devraient être IEEE754 compliant, et donc a priori donner le même résultat sur toutes les plateformes.
C'est donc bien un bug dans ton code.
Par contre, bien sûr, le cast int --> float ou double est autorisé.

Message cité 1 fois
Message édité par el muchacho le 13-03-2007 à 20:11:12
Reply

Marsh Posté le 13-03-2007 à 20:03:23    

Joel F a écrit :

On repart de 0:
 
1/ je fais un calcul sur des flottants sans representation exacte
2/ ce calcul n'a pas les mêmes résultats selon le compilo/plateforme/options de compil
 
Question : Pourquoi cette variabilité ...


wof, même en java, sans strictfp tu manges des bonnes différences.

Reply

Marsh Posté le 13-03-2007 à 21:39:01    

ok c'est noté :)

Reply

Marsh Posté le 13-03-2007 à 22:50:52    

Qques bémols.
 
Je n'ai pas dit que les troncations et autres casts étaient verbotten, ni invoqué une violation de la sacro-sainte IEEE 754, mais juste que les présomptions de l'auteur étaient bancales.
 
En fait il est relativement pénible d'être carré avec les flottants sur un x86, et facile de se tirer dans le pied, ne serait-ce qu'à cause de l'exotisme du x87 (et des ABI). Le laxisme du C/C++ n'arrange rien.  

Reply

Marsh Posté le 13-03-2007 à 23:31:55    

Ace17 a écrit :

Le monsieur te dit que c'est l'operation que tu fais faire au compilo qui est problematique, et que ton code est donc faux ... Si je fais des depassements de tableaux, je ne me plains pas comme quoi ca ne donne pas les memes resultats sur toutes les plateformes! Ici, c'est pareil.


Pas certain que l'analogie soit bonne. Un dépassement de tableaux, c'est un comportement indéfini. Je n'ai pas connaissance que les  opérations évoquées précedemment, soit classées "comportement indéfini".

Reply

Marsh Posté le 13-03-2007 à 23:33:18    

el muchacho a écrit :

Parce que la norme laisse volontairement dans le flou le cast float ou double --> int (et a fortiori avec size_t),


 
Qu'est-ce qui est flou dans le passage que j'ai quoté un peu plus haut ?
 

Citation :

histoire de bénéficier d'optims d'implémentation dans le cast statique.


Tu pourrais développer ?
 
 

Reply

Marsh Posté le 13-03-2007 à 23:43:22    

Joel F a écrit :

On repart de 0:
 
1/ je fais un calcul sur des flottants sans representation exacte
2/ ce calcul n'a pas les mêmes résultats selon le compilo/plateforme/options de compil
 
Question : Pourquoi cette variabilité ...


Penses aux transformations - déclenchées par une optimisation - du genre x / 2 --> x * 0.5, qui vont influer sur le résultat du calcul.
Il y a indirectement des réponses aux questions que tu te poses dans n1124.pdf, section <fenv.h>.

Reply

Marsh Posté le 14-03-2007 à 05:35:03    

++fab a écrit :

Qu'est-ce qui est flou dans le passage que j'ai quoté un peu plus haut ?

 
Citation :

histoire de bénéficier d'optims d'implémentation dans le cast statique.


Tu pourrais développer ?

 

<<la valeur "a peu près 5.000" peut être bien différente suivant les optimisations appliquées. Il n'y a rien dans le standard C++ fasse pour empêcher cela.>>

 

Ok, je n'étais pas correct.
C'est pas pour le cast qu'il y a un flou mais dans la représentation des flottants. C'est d'ailleurs un des arguments de l'arrière-garde du Fortran contre le C++ en calcul numérique. Une conséquence de ça, c'est que pour rendre les algos numériques le plus portable possible, les constantes flottantes sont souvent écrites en hexadécimal, mais l'exactitude de la répétition du résultat aux bits de poids faibles près sur des architectures différentes n'est pas garantie.


Message édité par el muchacho le 14-03-2007 à 05:44:59
Reply

Marsh Posté le 14-03-2007 à 08:55:46    

Quitte à penser pour un débile,  y a quelque chose qui me turlupine ...

 
Citation :


An rvalue of a floating point type can be converted to an rvalue of an integer type. The conversion truncates; that is, the fractional part is discarded.

 

Si ca c'est défini, je ne vois pas pourquoi la troncature d'un réel flottant ou double précision ne donne pas le même résultat partout.
Ca veut donc dire que :

 
Code :
  1. float f = 7.5;
  2. int i = f;
 

n'a pas de sens, contrairement à tout ce que l'on m'a enseigné ?

 

Je suis bien conscient des problèmes de représentation exacte etc ...
ca me fait quand mait gamberger que les optimsiations et/ou réécriture de code autour des calculs puissent faire diverger la sauce tant que ça.

 


++fab : merci pour le lien. Y a le même genre de doc pour le C++ dispo quelque part ?

Message cité 1 fois
Message édité par Joel F le 14-03-2007 à 09:02:08
Reply

Marsh Posté le 14-03-2007 à 09:31:19    

Encore une fois le problème n'a pas à voir avec une opération précise, mais le fait que dans le programme en question il n'y a aucune garantie sur les opérations qui vont intervenir, et ensuite aucune garantie sur la précision employée.
 
Par exemple pour le dernier,

Code :
  1. printf("%d\n",size_t(bmass(0.2,1)));


 
j'ai pu obtenir de g++ un truc équivalent à  

Code :
  1. printf([adresse de chaine], 5);


 
alors que dans le pire des cas au moins un appel (avec transfert de flottants), une division et une troncation auraient lieu le tout avec qques transferts possible entre x87/SSE et vice versa (par exemple si l'ABI exige le passage de flottants sur la pile x87, mais que le code est généré pour SSE).
Donc des chemins multiples, chacun avec des marges d'erreur différentes et vous escomptez une égalité... ça ne peut pas marcher.

Reply

Marsh Posté le 14-03-2007 à 09:39:25    

tbp a écrit :


Donc des chemins multiples, chacun avec des marges d'erreur différentes et vous escomptez une égalité... ça ne peut pas marcher.


 
donc c'est bien ce que je pensais, y a des trucs qui se passent dans mon dos entre ce que j'écris et ce que le compilateur effectue en réalité.
D'où les différences entre les exécutions sur des palteofrme/compilo différents ?

Reply

Marsh Posté le 14-03-2007 à 09:43:12    

Oui mais c'est la mauvaise façon de regarder le problème, c'est le programme & les attentes que vous en avez qui sont mal définies.
Et de manière générale on a intérêt à y regarder à 2 fois dès qu'une comparaison entre flottants intervient.

Reply

Marsh Posté le 14-03-2007 à 11:19:01    

tbp a écrit :

Oui mais c'est la mauvaise façon de regarder le problème, c'est le programme & les attentes que vous en avez qui sont mal définies.
Et de manière générale on a intérêt à y regarder à 2 fois dès qu'une comparaison entre flottants intervient.


 
Ok donc  :jap:

Reply

Marsh Posté le 14-03-2007 à 14:41:40    

Joel F a écrit :

Quitte à penser pour un débile,  y a quelque chose qui me turlupine ...

 
Citation :


An rvalue of a floating point type can be converted to an rvalue of an integer type. The conversion truncates; that is, the fractional part is discarded.

 

Si ca c'est défini, je ne vois pas pourquoi la troncature d'un réel flottant ou double précision ne donne pas le même résultat partout.
Ca veut donc dire que :

 
Code :
  1. float f = 7.5;
  2. int i = f;
 

n'a pas de sens, contrairement à tout ce que l'on m'a enseigné ?


Le C++ l'autorise, c'est pas pour ça qu'il faut l'utiliser. Parfois c'est pratqiue, mais en général, le typage non strict, c'est mal.[:spamafote]


Message édité par el muchacho le 14-03-2007 à 14:42:17
Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed