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

Calculs du point de vue de la norme

57 réponses
Avatar
jul
Salut,

Je programme une application qui fait un fort usage des calculs avec
des valeurs d=E9cimales (=E0 virgule) avec les types float ou double. Il
ne me semble pas que ces calculs sont pris directement en charge par
le(s) processeur(s). Ma question est donc la suivante : la norme
impose-t-elle l'identit=E9 des r=E9sultats de calculs entre diff=E9rents
compilateurs ? Si oui, au moyen de quel(s) crit=E8re(s) ?

Je pr=E9cise pour bien me faire comprendre. Il facile d'exiger qu'un
compilateur calcule de mani=E8re correcte sur les entiers (1+1 doit
toujours =EAtre =E9gal =E0 2), ce qui est en soi une garantie
(ext=E9rieure) de l'identit=E9 des r=E9sultats. Mais lorsque le calcul a
lieu sur des nombres d=E9cimaux, le r=E9sultat est souvent arrondi. Il
n'y a pas alors de "r=E9sultat juste" (ex. 1/3 n'a pas d'expression
d=E9cimale finie juste, m=EAme si certaines sont meilleures que
d'autres). Comment sait-on que la mani=E8re de calculer et d'arrondir
sera la m=EAme sur chaque compilateur ?

Merci pour vos =E9claircissements.
Jul.

10 réponses

1 2 3 4 5
Avatar
Ploc
jul wrote:
Salut,

Je programme une application qui fait un fort usage des calculs avec
des valeurs décimales (à virgule) avec les types float ou double. Il
ne me semble pas que ces calculs sont pris directement en charge par
le(s) processeur(s). Ma question est donc la suivante : la norme
impose-t-elle l'identité des résultats de calculs entre différents
compilateurs ? Si oui, au moyen de quel(s) critère(s) ?

Je précise pour bien me faire comprendre. Il facile d'exiger qu'un
compilateur calcule de manière correcte sur les entiers (1+1 doit
toujours être égal à 2), ce qui est en soi une garantie
(extérieure) de l'identité des résultats. Mais lorsque le calcul a
lieu sur des nombres décimaux, le résultat est souvent arrondi. Il
n'y a pas alors de "résultat juste" (ex. 1/3 n'a pas d'expression
décimale finie juste, même si certaines sont meilleures que
d'autres). Comment sait-on que la manière de calculer et d'arrondir
sera la même sur chaque compilateur ?


Normalement, c'est la norme IEEE 754 (representation des flottants) qui
assure ca, mais rien a voir avec c++.

Avatar
jul
Merci pour l'info. Ma question est maintenant : cette norme est-elle
généralement suivie par les logiciels/matériels, et en particulier
par C++ ?

Jul.
Avatar
kanze
jul wrote:

Je programme une application qui fait un fort usage des
calculs avec des valeurs décimales (à virgule) avec les types
float ou double.


Tu crois ? Je ne connais pas de machine moderne où les float ou
les double sont des types décimaux. Binaire, la plupart du
temps, ou héxadécimal, mais pas décimaux.

Il ne me semble pas que ces calculs sont pris directement en
charge par le(s) processeur(s).


Ça dépend du hardware, mais sur tous les processeurs
généralistes aujourd'hui, les calculs flottants sont implémentés
directement dans l'hardware.

Ma question est donc la suivante : la norme impose-t-elle
l'identité des résultats de calculs entre différents
compilateurs ?


Pas du tout. Elle n'impose même pas une identité entre deux
calculs identiques dans le même programme -- ce n'est pas rare
de trouver des cas comme :

double i = 1.0, j = 3.0 ;
double test = i / j ;
assert( test == i / j ) ;

Non seulement le non-échec de l'assert n'est pas garantie par
la norme, il y a des systèmes où il risque fort d'échouer.

Si oui, au moyen de quel(s) critère(s) ?

Je précise pour bien me faire comprendre. Il facile d'exiger
qu'un compilateur calcule de manière correcte sur les entiers
(1+1 doit toujours être égal à 2), ce qui est en soi une
garantie (extérieure) de l'identité des résultats. Mais
lorsque le calcul a lieu sur des nombres décimaux, le résultat
est souvent arrondi. Il n'y a pas alors de "résultat juste"
(ex. 1/3 n'a pas d'expression décimale finie juste, même si
certaines sont meilleures que d'autres). Comment sait-on que
la manière de calculer et d'arrondir sera la même sur chaque
compilateur ?


Je comprends bien ton problème. Le réponse, c'est que dans les
faits, la manière de calculer et d'arrondir n'est pas la même
d'un compilateur à l'autre -- elle n'est même pas la même selon
les options du compilateur, ni même entre deux sous-expressions
dans le même programme : dans mon exemple ci-dessus, si
l'expression i / j se trouve dans des expressions plus complexe,
parfois elle aurait la valeur qu'on trouve en test, ci-dessus,
et parfois la valeur qui n'est pas égale à test.

--
James Kanze GABI Software
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
kanze
Ploc wrote:
jul wrote:

Je programme une application qui fait un fort usage des
calculs avec des valeurs décimales (à virgule) avec les
types float ou double. Il ne me semble pas que ces calculs
sont pris directement en charge par le(s) processeur(s). Ma
question est donc la suivante : la norme impose-t-elle
l'identité des résultats de calculs entre différents
compilateurs ? Si oui, au moyen de quel(s) critère(s) ?

Je précise pour bien me faire comprendre. Il facile d'exiger
qu'un compilateur calcule de manière correcte sur les
entiers (1+1 doit toujours être égal à 2), ce qui est en soi
une garantie (extérieure) de l'identité des résultats. Mais
lorsque le calcul a lieu sur des nombres décimaux, le
résultat est souvent arrondi. Il n'y a pas alors de
"résultat juste" (ex. 1/3 n'a pas d'expression décimale
finie juste, même si certaines sont meilleures que
d'autres). Comment sait-on que la manière de calculer et
d'arrondir sera la même sur chaque compilateur ?


Normalement, c'est la norme IEEE 754 (representation des
flottants) qui assure ca, mais rien a voir avec c++.


Elle est loin d'être universale, et elle laisse un certain
nombre de libertés à l'implémentation aussi. Donc, des deux
processeurs que j'ai sous la main (un PC sous Linux et un Sun
Sparc sous Solaris), le programme :

#include <assert.h>

int
main()
{
double i = 1.0, j = 3.0 ;
double test = i / j ;
assert( test == i / j ) ;
return 0 ;
}

fait un core dump sur le PC, mais pas sur le Sparc. Bien que
tous les deux utilisent IEEE 754 pour les flottants. (Je crois
qu'il y a une option de g++ pour empècher le core dump. Mais
elle n'est pas active par défaut, et elle rallentit le
processeur pas mal.)

--
James Kanze GABI Software
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
Jean-Marc Bourguet
"jul" writes:

Je programme une application qui fait un fort usage des calculs avec
des valeurs décimales (à virgule) avec les types float ou double.


Ouille, il y a de la confusion.

Les types decimaux sont des types dont la representation est basee sur
des chiffres decimaux. Si on exclus les problemes de limites, ca ne
change le comportement observable que pour des nombres non entiers.

On a plusieurs types de nombres non entiers. En restant avec des
representations en espace constant, on peut avoir:

- des rationnels ou le numerateur et le denominateur sont
explicitement donnes dans le nombre; c'est relativement rare (Common
Lisp en a)

- des rationnels ou le denominateur est implicite. C'est les nombres
en virgule fixe. Il sont decimaux si le denominateur est une
puissance de 10, binaires si c'est une puissance de 2. Ce genre de
representation a borne constante de l'erreur absolue.

- on represente en virgule fixe le logarithme du nombre. C'est
interessant, on a une borne constante de l'erreur relative. C'est
complique a implementer si on veut pouvoir faire des additions.

- on represente un nombre en deux partie: une partie fractionnaire
(alias mantisse) entre 0 et un en virgule fixe et un exposant (qui
correspond a la partie entiere du logarithme). On a une bonne
approximation d'un systeme avec une borne constante de l'erreur
relative, mais plus simple a implementer qu'une representation
logarithmique. C'est les nombres en virgules flottantes. A
nouveau, binaire si la base est 2, decimal si la base est 10,
hexadecimal si la base est 16. La version binaire est plus proche
de la representation logarithmique et est donc preferee des
numericiens (pour autant que je les ai compris).

float et double sont classiquement des nombres en virgule flottante
binaires.

Il ne me semble pas que ces calculs sont pris directement en charge
par le(s) processeur(s).


Les processeurs ont generalement un support pour des entiers (qui est
un cas degenere de la virgule fixe) et des nombres en virgule
flottante binaires.

Ma question est donc la suivante : la norme impose-t-elle l'identité
des résultats de calculs entre différents compilateurs ?


Non. Je ne connais pas de langage ou c'est le cas. Java a tente
d'impose le comportement en se referant a IEEE 754 mais est revenu en
arriere au moins dans des cas limites (_denormaux_, gestion des
infinis, _NaN_, ...) pour des raisons de performance. Vouloir
l'identite de resultats sur des calculs en virgule flottante, c'est
serieusement contraindre les implementations.

Je précise pour bien me faire comprendre. Il facile d'exiger qu'un
compilateur calcule de manière correcte sur les entiers (1+1 doit
toujours être égal à 2), ce qui est en soi une garantie
(extérieure) de l'identité des résultats. Mais lorsque le calcul a
lieu sur des nombres décimaux, le résultat est souvent arrondi. Il
n'y a pas alors de "résultat juste" (ex. 1/3 n'a pas d'expression
décimale finie juste, même si certaines sont meilleures que
d'autres). Comment sait-on que la manière de calculer et d'arrondir
sera la même sur chaque compilateur ?


Vu la prevalance des flottants binaires, 1/10 n'a souvent pas de
representation exacte en flottant.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Fabien LE LEZ
On 15 May 2006 05:28:51 -0700, "jul" :

la norme
impose-t-elle l'identité des résultats de calculs entre différents
compilateurs ?


Non. Si tu as besoin de calculs fiables, les types double et float
sont à proscrire.

Il existe (je crois) des spécialistes qui savent effectuer des calculs
avec ces types, mais AMHA ils sont rares -- et a priori, nous n'en
faisons pas partie.

Avatar
Sylvain
jul wrote on 15/05/2006 14:28:

Je programme une application qui fait un fort usage des calculs avec
des valeurs décimales (à virgule) avec les types float ou double.


(à moi-même, le dernier "fil flottant" a été animé...)

Il ne me semble pas que ces calculs sont pris directement en charge
par le(s) processeur(s).


si, par l'unité idoine du proc.

(seuls les vieux compilos des vieilles machines dépourvus de "co-proc
arith." (disait-on à l'époque) utilisaient des libs math. d'émulation,
je ne pense pas que vous rencontrerez cela aujourd'hui - sauf peut être
pour du code embarqué.)

Ma question est donc la suivante : la norme impose-t-elle
l'identité des résultats de calculs entre différents
compilateurs ? Si oui, au moyen de quel(s) critère(s) ?


la question n'est dès lors plus "entre différents compilateurs" mais
entre différents CPU.
bcp, surement pas tous, utilisent les normes IEEE déjà cités, donc ils
sont censés se comporter à peu près pareil ... pour autant l'identité
n'est pas garantie et _surtout_ ne doit pas être cherchée (un code
numérique ne contient jamais d'opérateur == ...ok sauf pour les boucles)

[...] Il facile d'exiger qu'un compilateur calcule de manière
correcte sur les entiers [...]. Mais lorsque le calcul a
lieu sur des nombres décimaux, le résultat est souvent arrondi.


toujours même sauf cas particuliers (0, NaN, inf).

Comment sait-on que la manière de calculer et d'arrondir
sera la même sur chaque compilateur ?


pour un compilo et/ou un hard utilisant la même représentation (c'est
assez facilement atteint) et le même mode opératoire de calcul (le même
cablage) (ça c'est jamais le cas - chaque fondeur choisit ses propres
tables) la manière de calculer et d'arrondir est identique; mais il
existe des différences (subtiles) dans le hard qui influenceront les
arrondis et le résultat final.

si votre question était donc simplement: peut-on garantir des identités,
la réponse est non, non pour un même programme sur le même CPU, non pour
le même programme sur 2 CPU/compilateurs différents.

si votre question est: peut-on obtenir les "mêmes résultats" entre
différents compilateurs et/ou CPU, la réponse est oui parce qu'on ne
doit pas parler "d'identité" mais simplement de résultat connu à une
précision donnée.

pour l'anecdote, j'ai fait du calcul numérique pdt en gros 8 ans, et les
codes (certain bien conséquent) donnaient le même résultat _à 10^-8
près_ sur Pentium2 / Studio5, PowerPC601 / CodeWarrior, Sun Sparc2 / gcc
et f77.

la difficulté d'un code numérique n'est jamais de chercher une identité
(cela ne doit jamais être fait, bis), mais de coder tout un cycle de
calcul de manière à conserver le maximum de précision (pas de nombres
dénormaux intermédiaires) et de ne pas générer de débordements (la
matrice ((0.000??, 0.000??), (inf, inf)) a mathématiquement un
déterminant de 0, mais informatiquement elle est imprévisible).

selon la nature de votre calcul, vous pourrez vous en tirer avec une
simple évaluation des valeurs limites de toutes les opérations et écrire
l'ensemble de manière adéquate; avec la complexité des expressions, le
langage peut devenir une aide avec par exemple des représentations
symboliques (fraction, matrice, exp. linéaire, ...) qui se simplifient
et/ou se réorganisent.

Sylvain.

Avatar
Ploc
kanze wrote:

Elle est loin d'être universale, et elle laisse un certain
nombre de libertés à l'implémentation aussi. Donc, des deux
processeurs que j'ai sous la main (un PC sous Linux et un Sun
Sparc sous Solaris), le programme :

#include <assert.h>

int
main()
{
double i = 1.0, j = 3.0 ;
double test = i / j ;
assert( test == i / j ) ;
return 0 ;
}



ah tiens, amusant. Je fais jamais ca (faire un calcul dans un assert),
mais c'est interessant.
Faudra que je regarde ce que donne le code assembleur, parce qu'en
faisant le calcul dans une variable avant, ca fonctionne (j'ai pas dit
portable, malgre ce que je pensais...).

Avatar
kanze
Ploc wrote:
kanze wrote:

Elle est loin d'être universale, et elle laisse un certain
nombre de libertés à l'implémentation aussi. Donc, des deux
processeurs que j'ai sous la main (un PC sous Linux et un Sun
Sparc sous Solaris), le programme :

#include <assert.h>

int
main()
{
double i = 1.0, j = 3.0 ;
double test = i / j ;
assert( test == i / j ) ;
return 0 ;
}


ah tiens, amusant. Je fais jamais ca (faire un calcul dans un
assert), mais c'est interessant.


L'assert n'y est pour rien. Mon point, c'était simplement que
selon l'utilisation qu'on en fait, 1.0/3.0 ne donne pas toujours
le même résultat. La façon la plus simple et la plus sûr d'avoir
un résultat différent, c'est de le mettre dans une variable de
type double dans un cas, et non dans l'autre, dans des
expressions très simples. Dans une expression plus compliquée,
le résultat pourrait dépendre de si le compilateur a pu garder
tous les résultats intermédiaires en régistres, ou s'il a fallu
qu'ils en sauvent certains dans des variables temporaires en
mémoire. Ce qui en son tour pourrait dépendre du niveau
d'optimisation.

Faudra que je regarde ce que donne le code assembleur, parce
qu'en faisant le calcul dans une variable avant, ca fonctionne
(j'ai pas dit portable, malgre ce que je pensais...).


Une fois que tu stockes dans une variable nommée, tu es garanti
la précision de la variable pour ce résultat -- ni plus, ni
moins. Mais la norme permet les calculs et les résultats
intermédiaire d'avoir une précision plus grande. Donc, si
j'écris quelque chose du genre :

double i = 1.0, j = 3.0 ;
double test = i / j ;
double t1 = test * 3.1, t2 = i / j * 3.1 ;

t1 et t2 ne sont pas égaux. Ou ils le sont -- ça change selon
les options d'optimisation.

Je ne suis pas spécialiste dans le domaine, mais j'ai comme une
impression que ce genre de chose pourrait rendre l'évaluation de
la stabilité d'un calcul plutôt difficile.

--
James Kanze GABI Software
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
kanze
Fabien LE LEZ wrote:
On 15 May 2006 05:28:51 -0700, "jul" :

la norme
impose-t-elle l'identité des résultats de calculs entre
différents compilateurs ?


Non. Si tu as besoin de calculs fiables, les types double et
float sont à proscrire.

Il existe (je crois) des spécialistes qui savent effectuer des
calculs avec ces types, mais AMHA ils sont rares -- et a
priori, nous n'en faisons pas partie.


Pour quels nous ? En ce qui me concerne, tu as certainement
raison -- je te fais confiance pour ton évaluation de toi-même
aussi. Mais d'après ce que j'ai compris, je crois que Gaby
saurait le faire, et je crois que Jean-Marc, s'il s'y mettait,
aussi.

Ce que je crois qu'on pourrait dire, c'est que même quand il
s'agit d'un spécialiste, qui sait le faire, il faut qu'il
reflechisse. Avoir un résultat fiable ne va jamais de soi.

Dans le cas général. Je me sers volentiers des flottants pour
l'affichage d'un percentage, par exemple. Avec au maximum trois
ou quatre chiffres de précision. Si x et y sont des entiers (int
ou long, par exemple), j'ai une assez grande confiance que
l'expression 100.0 * x / y me donnerait un résultat assez fiable
pour un affichage à quatre chiffres. (Mais c'est à peu près ma
limite.)

--
James Kanze GABI Software
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


1 2 3 4 5