L'unité à virgule flottante (FPU) traditionnelle des processeurs x86 peut être configurée pour arrondir les résultats en précision étendue (64 bits de mantisse) ou en double précision (53 bits de mantisse); l'arrondi interne en simple précision est également supporté, mais n'est pas utilisé en pratique. Certains systèmes d'exploitation (presque tous?), comme FreeBSD, NetBSD et Microsoft Windows, ont choisi de configurer le processeur pour que, par défaut, l'arrondi se fasse en double précision. En revanche, sous Linux, l'arrondi se fait en précision étendue (je ne connais pas d'autre système ayant le même comportement). C'est un mauvais choix pour les raisons données ci-dessous.
Note: les instructions SSE2 ne sont pas concernées par ces problèmes.
Avant de donner les inconvénients de la précision étendue, notons que la précision étendue (optionnelle) a été introduite dans la norme IEEE 754 essentiellement pour permettre une implémentation précise en double précision des fonctions élémentaires de la bibliothèque mathématique (exponentielle, logarithme, fonctions trigonométriques et hyperboliques, etc.), donc pour des cas très particuliers.
Le fichier /usr/include/fpu_control.h sous Linux/x86 dit:
#define _FPU_EXTENDED 0x300 /* libm requires double extended precision. */
mais des tests (avec
mpcheck
notamment) montrent que la bibliothèque mathématique ne semble pas avoir
besoin de la précision étendue: la double précision semble suffisante.
Ou est-ce que quelqu'un a un contre-exemple?
Mise à jour: comme montré dans le
bug
706 de la glibc, la fonction pow est peu
précise dans certains cas, et encore moins précise quand le processeur
est configuré en double précision.
Même si dans un langage de programmation, on utilise un type correspondant à la double précision (par exemple, le type double sur la plupart des implémentations C), un processeur calculant en précision étendue en interne ne donnera pas nécessairement les mêmes résultats qu'un processeur calculant en double précision. Par exemple, considérons les deux nombres flottants en double précision x = 253 + 2 et y = 1 − 2−16, et leur somme en arrondi au plus près z = x + y. Sur un système travaillant en double précision, le résultat est l'arrondi de 253 + 3 − 2−16, c'est-à-dire 253 + 2 (plus proche du résultat exact que ne l'est le nombre machine suivant 253 + 4). Sur un système travaillant en précision étendue, le résultat sera d'abord arrondi en précision étendue, c'est-à-dire 253 + 3, puis arrondi en double précision (lors de son stockage en mémoire, par exemple), c'est-à-dire 253 + 4 (en utilisant la règle de l'arrondi pair, puisque 253 + 3 est le milieu de deux nombres machine consécutifs). Le résultat final obtenu est donc différent.
Note: ce phénomène, appelé double arrondi, ne se produit qu'en arrondi au plus près, mais c'est le mode d'arrondi généralement utilisé.
D'abord, la précision étendue pose des problèmes de portabilité, même si toutes les précautions sont prises au niveau des types utilisés, notamment parce que le support de la précision étendue est facultatif, et en pratique, de nombreux processeurs ne la supportent pas. Notons également que la précision étendue n'est pas complètement normalisée. Même si le fait d'obtenir exactement le même résultat sur deux machines différentes n'est pas considéré comme important et que les exigences sont liées seulement à la précision des résultats, la précision étendue est inutile, car un programme portable doit supposer le pire des cas.
Si la portabilité n'est pas importante et que le logiciel doit calculer en précision étendue sur une machine donnée (en utilisant le type long double en C, par exemple), le logiciel a toujours la possibilité de configurer le processeur en arrondi en précision étendue (de manière non portable malheureusement, mais de toute façon, la portabilité a déjà été sacrifiée).
Concernant la portabilité, il est donc préférable d'avoir partout un processeur dont le comportement par défaut est d'arrondir en double précision, sans inconvénient majeur pour les programmes nécessitant de la précision étendue.
Le but de la précision étendue est de permettre d'avoir des opérations de base plus précises pour obtenir au final des résultats plus précis. Mais la plupart des programmes n'ont pas été écrits spécialement pour la précision étendue, et en général, aucune garantie n'est apportée. Par exemple, de nombreux programmes écrits en C utilisent le type double, et même si les calculs intermédiaires s'effectuent en précision étendue, certains résultats peuvent être automatiquement convertis en double précision, sans que ces conversions puissent être contrôlées.
La précision étendue peut être nécessaire à certains logiciels. Mais dans ce cas, il est important de pouvoir prouver les majorations d'erreur et ne pas se reposer sur des suppositions qui pourraient se révéler fausses. En particulier, l'utilisation du la précision étendue avec le type double ne permettra pas d'obtenir de meilleures bornes d'erreur garanties; au contraire, ces bornes peuvent être pires à cause du problème du double arrondi. La seule possibilité est d'écrire du code non portable (comme indiqué ci-dessus), que la précision par défaut soit de la double précision ou de la précision étendue.
Pour les autres logiciels, le choix de la précision par défaut a peu d'importance, mais l'arrondi en double précision est préférable pour les preuves liées aux majorations d'erreur.
Certains algorithmes se basent sur la propriété d'arrondi correct et peuvent devenir complètement faux si un double arrondi peut se produire. Concernant ce point, mon papier The Euclidean division implemented with a floating-point division and a floor donne un exemple particulièrement utile pour des programmes écrits en ECMAScript (souvent mentionné en tant que Javascript) ou utilisant XPath.
D'autre part, une même expression flottante évaluée deux fois peut produire des résultats différents et faire échouer une comparaison. C'est particulièrement vrai avec gcc, qui ne respecte pas certains points de la norme C. Un certain nombre de personnes ont été confrontées à ces problèmes; cf le bug 323 de GCC (qui sera probablement corrigé dans GCC 4.5, dès lors que les bonnes options de compilation seront utilisées, comme demander d'être conforme à la norme C) et les doublons (qui ne sont en fait pas toujours de vrais doublons, car il ne s'agit pas toujours du même problème, même si la cause première est la précision étendue), ainsi que mon programme tst-ieee754.c.
Enfin, certaines normes comme Java et XPath (ainsi que XSLT, puisque se basant en partie sur XPath) imposent la double précision IEEE avec arrondi correct, de manière à obtenir des résultats complètement reproductibles sur des machines différentes ou avec des logiciels différents. L'arrondi en précision étendue par défaut rend leur implémentation non portable (comme expliqué plus haut); et en pratique, la plupart des implémentations ne changent pas la précision interne du processeur et sont donc souvent buggées (cf ci-dessous).
Note: le Java a aussi un type simple précision (float), mais le choix entre arrondi en double précision et arrondi en précision étendue ne règle en rien les problèmes d'arrondi concernant ce type; dans les deux cas, sans code spécial, l'implémentation serait incorrecte sur ce point. Cependant, on peut supposer qu'aucun développeur soucieux du comportement numérique de son programme n'utilise le type float pour les calculs, surtout que ce ne serait pas plus rapide.
Testez l'arithmétique
XPath avec cette feuille de styles
XSLT, à appliquer sur n'importe quel fichier
XML, par exemple sur elle-même. Les processeurs
XSLT suivants donnent un résultat incorrect (message
Not a conforming XPath implementation.
) sous
Linux/x86:
LibXSLT (commande xsltproc). Dernière version testée: 1.1.7, sous Debian/unstable. Rapports de bug: bug 123297 sur le BTS de GNOME, bug 206549 sur le BTS de Debian (fermé), bug 247465 sur le BTS de Debian.
Sablotron (commande sabcmd). Dernière version testée: 1.0, sous Debian/unstable. Rapport de bug: bug 257843 sur le BTS de Debian.
Xalan-C++ (commande xalan). Dernière version testée: 1.8.0, sous Debian/unstable.
Testez l'arithmétique de votre JVM avec cette classe Java (source Java). Résultat correct:
$ java test z = 9.007199254740994E15 d = 0.0
Résultat incorrect, lorsque le processeur arrondit en précision étendue:
$ java test z = 9.007199254740996E15 d = 2.0
L'interpréteur GNU gij (dernière version testée: 4.0.3 prerelease) donne ce résultat incorrect sous Linux/x86. Bug 255525 sur le BTS de Debian. Bug 16122 sur GCC Bugzilla.
Ce bug a été corrigé dans les JVM suivantes:
SableVM. Corrigé dans la version 1.1.7 (c'était le bug 1 sur le BTS de SableVM). Corrigé dans le paquet Debian 1.1.6-4 (cf bug 255524 sur le BTS de Debian).
JamVM. Corrigé dans la version 1.2.1 (cf bug 260410 sur le BTS de Debian).
Kaffe Virtual Machine. Corrigé dans le paquet Debian 1.1.6-2 (cf bug 255502 sur le BTS de Debian).
CACAO. Corrigé dans le paquet Debian 0.94-2 (la version 0.94-1 était incorrecte, mais pas les précédentes, cf bug 350729 sur le BTS de Debian).
Note: les JVM de Sun et d'IBM donnent toujours un résultat correct.
Pour de plus amples informations sur Java et les calculs numériques: Improving Java for Numerical Computation (en particulier, voir la section Use of floating-point hardware) sur le site web JavaNumerics, et Updates to the Java Language Specification for JDK Release 1.2 Floating Point.
Reste le problème de la simple précision (est-elle souvent utilisée?). Les développeurs de compilateurs JIT peuvent être intéressés par l'article Optimizing precision overhead for x86 processors de Takeshi Ogasawara, Hideaki Komatsu et Toshio Nakatani, dans Software — Practice and Experience, 34(9):875–893, juillet 2004.
Test de l'arithmétique Javascript de votre navigateur: . La valeur 0 est le résultat correct. Si vous obtenez 2, cela signifie probablement que l'implémentation Javascript de votre navigateur utilise de la précision étendue, et c'est incorrect (pour de plus amples informations, cf la section 8.5 des spécifications du langage ECMAScript). Si vous n'obtenez rien du tout, cela signifie que votre navigateur ne supporte pas Javascript ou que Javascript a été désactivé.
Les navigateurs suivants donnent le résultat incorrect 2 sous Linux/x86:
Mozilla (dernière version testée: 1.8b Gecko/20050114) et Firefox (dernière version testée: 1.5 Gecko/20060110). Bug 264912 sur Bugzilla et bug 309797 sur le BTS de Debian.
Opera (dernière version testée: 8.51).
Un article intéressant (en anglais) de S. W. Whiteley sur Linux et la précision étendue. Note: il a été écrit en août 2001, il ne mentionne donc pas les nouveautés depuis cette date.
L'article What Every Computer Scientist Should Know About Floating-Point Arithmetic de D. Goldberg, avec l'addendum Differences Among IEEE 754 Implementations (article en PDF, sans l'addendum).