Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Précision des double

3 réponses
Avatar
AG
Bonjour,

Le programme ci-dessous ne me donne pas les même résultats avec Visual
C++ et g++. Je ne suis qu'a moitié surpris, mais je ne sais pas
expliquer pourquoi. J'imagine que c'est une différence dans la taille
des données des calculs intermédiaires ? Une explication, et un
conseil pour si j'avais envie de faire un code qui se comporte de la
même manière quel que soit la plateforme ?

Merci.
AG.

#include <vector>
#include <iostream>
#include <cmath>
#include <iomanip>
#include <limits>


using namespace std;

#define PRECISION_AG 20
#define NB_DIGIT_AG (8+(PRECISION_AG))

int main(void)
{

unsigned long a1 = 1651210449;
unsigned long max = std::numeric_limits<unsigned long>::max();
double x = (double) a1;
double x1 = ((double)a1)/max;


cout.setf(std::ios::scientific);
cout.precision(PRECISION_AG);

cout << "max=" << max << "\n";
cout << "x = " << setw(NB_DIGIT_AG) << x << "\n";
cout << "x1= " << setw(NB_DIGIT_AG) << x1 << "\n";

return 0;
}

3 réponses

Avatar
James Kanze
On Apr 16, 7:06 pm, "AG" wrote:
Le programme ci-dessous ne me donne pas les même résultats avec Visual
C++ et g++.


Et moi, j'ai des résultats différents entre g++ et Sun CC, sur
un Sparc. Ce qui m'étonnait un peu, parce que le Sparc n'a pas
le problème de la précision étendue sur les valeurs
intermédiaires.

(Tu ne l'as pas dit, mais je suppose que les deux compilateurs
tournaient sur la même machine. Une architecture IA-32, en
l'occurance.)

Je ne suis qu'a moitié surpris, mais je ne sais pas
expliquer pourquoi. J'imagine que c'est une différence dans la taille
des données des calculs intermédiaires ?


En fait, pas cette fois-ci.

Une explication, et un
conseil pour si j'avais envie de faire un code qui se comporte de la
même manière quel que soit la plateforme ?


Pour faire du code qui se compore de la même manière quel que
soit la plateforme, il n'y a qu'une solution : ne pas se servir
des flottants. Même Java y a rénoncé quand il y a des flottants.
Mais ce n'est pas le problème dans ce cas précis.

Quant à l'explication...

#include <vector>
#include <iostream>
#include <cmath>
#include <iomanip>
#include <limits>

using namespace std;

#define PRECISION_AG 20


Attention : un double IEEE n'a que 17 chiffres décimaux de
précision. Tout ce que tu démandes en plus, c'est du bruit.

#define NB_DIGIT_AG (8+(PRECISION_AG))

int main(void)
{
unsigned long a1 = 1651210449;
unsigned long max = std::numeric_limits<unsigned long>::max();
double x = (double) a1;
double x1 = ((double)a1)/max;


En principe, l'affectation doit forcer l'arrondi du résultat en
double, même si le calcul a une précision supplémentaire. Si tu
affectes tous les résultats intermédiaires, et que tu n'affiches
jamais avec plus de DBL_MAX de précision, tous les compilateurs
qui utilise les doubles IEEE doivent donner les mêmes résultats.
(Il se peut, en revanche, que tu sois obligé à utiliser des
options supplémentaires pour imposer la conformité ici.
-ffloat-store avec g++, par exemple.)

cout.setf(std::ios::scientific);
cout.precision(PRECISION_AG);

cout << "max=" << max << "n";
cout << "x = " << setw(NB_DIGIT_AG) << x << "n";
cout << "x1= " << setw(NB_DIGIT_AG) << x1 << "n";

return 0;
}


J'y ai ajouté une fonction qui fait un dump des octets des
floats, que j'appelle sur x et x1 après les sorties ci-dessus.
Après diverses essais (Sun CC, g++, VC++, sur Sparc et sur PC,
et sous Linux et sous Windows, avec différentes versions, avec
-ffloat-store et -fno-float-store avec g++), j'ai constaté
que :

-- L'option de g++ ne change jamais rien, ici.

-- Quelque soit le compilateur ou le système, la valeur dans x1
(c-à-d les bits mêmes, vue par le dump hex) est toujours
exactement identique.

-- Que le résultat affiché par g++ pour x2 n'était pas le même
selon la version.

En fait, j'ai eu trois affichages différents pour x1 : un pour
g++ 3.4.0 sur Sparc, un pour VC++ (celui de VS 2005), et un pour
tous les autres : Sun CC 5.1 et 5.8 sur Sparc, g++ 4.1.0 sur
Sparc, g++ 3.4.6 et g++ 4.1.0 sous Linux, et g++ 3.3.3 sous
Windows. D'après les apparences :

-- g++ 3.4.0 avant une erreur qui n'apparaissait que sur Sparc,
ou au moins, n'apparaissait pas sur l'architecture Windows,
et qui a été depuis corrigée. C'est une spéculation de ma
part, mais le fait que la version plus récent s'aligne avec
les autres résultats me le fait penser.

-- VC++ n'affiche pas les chiffres sans signification, Sun CC
et g++ le font. A priori, je dirais que c'est VC++ qui a
raison ici ; au delà du dix-septième chiffre, c'est du
bruit, et non de l'information. Mais je ne suis pas vraiment
expert dans le domaine, pas du tout. Alors, il se peut qu'il
y a des raisons que je ne connais pas pour justifier les
chiffres supplémentaires. (Il me semble avoir lu quelque
part que selon IEEE, on ne doit pas les afficher, mais là
aussi, c'est un souvenir assez vague, et je suis assez peu
expert pour pouvoir l'avoir mal compris aussi.)

Enfin, il faut dire que tu as cherché le problème toi-même, en
démandant la conversion d'un double avec plus de DBL_DIG
précision, qui n'a pas de sens. On peut discuter si c'est VC++
ou les autres qui ont raison en ce qui concerne les chiffres
supplémentaires, mais si tu n'avais pas démandé des chiffres
bidon, tu ne l'aurais pas vu la différence. Avec DBL_DIG
précision, tous les compilateurs (y compris g++ 3.4.0 sur Sparc)
affichent exactement la même chose.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
AG
(Tu ne l'as pas dit, mais je suppose que les deux compilateurs
tournaient sur la même machine. Une architecture IA-32, en
l'occurance.)
Oui .



Attention : un double IEEE n'a que 17 chiffres décimaux de
précision. Tout ce que tu démandes en plus, c'est du bruit.
Oui je savais. Mais je m'attendais à n'avoir que des zéros au-dela de

la 17ième décimale.
Avec éventuellement une différence sur le dernier bit. Ce qui m'a
perturbé, c'est de voir gcc me donner jusqu'a 42 décimales (140bits)
après la virgule, alors que le double est codé sur 64 bits. Je me
demande d'ou viennent ces chiffres.


(Il se peut, en revanche, que tu sois obligé à utiliser des
options supplémentaires pour imposer la conformité ici.
-ffloat-store avec g++, par exemple.)
J'ai aussi testé cette option, et je suis arrivé à la même conclusion

que toi. ça ne change rien dans ce cas là. J'ai aussi vu un poste de
Gabriel Dos Reis qui indiquait que cette option ne fonctionnait pas
tout le temps... je n'ai pas approfondis.

-- Que le résultat affiché par g++ pour x2 n'était pas le même
selon la version.
J'imagine que tu parlais de x1 ici et de x à la place de x1 :-)


Enfin, il faut dire que tu as cherché le problème toi-même, en
démandant la conversion d'un double avec plus de DBL_DIG
précision, qui n'a pas de sens. On peut discuter si c'est VC++
ou les autres qui ont raison en ce qui concerne les chiffres
supplémentaires, mais si tu n'avais pas démandé des chiffres
bidon, tu ne l'aurais pas vu la différence. Avec DBL_DIG
précision, tous les compilateurs (y compris g++ 3.4.0 sur Sparc)
affichent exactement la même chose.
Je vais vérifier cela. Mon générateur de bruit gaussien ne donne pas

la même chose suivant le compilateur. il faut que je trouve d'ou ça
vient.
j'utilise le code ci-dessus, plus 4*atan2(1.,1.) pour Pi (j'ai
vérifié, ça donne la même chose sur les deux compilos), et les
fonction log, cos, sin et sqrt. C'est peut être là que ça va coincer ?

En tout cas merci.

Alexandre.

Avatar
Olivier Miakinen

Attention : un double IEEE n'a que 17 chiffres décimaux de
précision. Tout ce que tu démandes en plus, c'est du bruit.
Oui je savais. Mais je m'attendais à n'avoir que des zéros au-dela de

la 17ième décimale.


Ça aurait pu être le cas si IEEE stockait les nombres en BCD (décimal
codé en binaire), mais vu que c'est stocké en binaire il n'y a pas de
relation directe entre des zéros binaires (stockés ou implicites) et
des zéros décimaux. Éventuellement, on doit pouvoir s'attendre à ne
trouver que des zéros au delà de la 52e décimale pour des nombres
compris entre 1 et 2.

Par exemple, 1.0000000000000000000000000000000000000000000000000001
en base 2 vaut 1.0000000000000002220446049250313080847263336181640625
en base 10.