[Document initialement écrit en 2004.]
L'unité à virgule flottante (FPU) traditionnelle des processeurs x86 (a.k.a. x87) peut être configurée pour arrondir ses résultats en précision étendue (significande sur 64 bits) ou en double précision (significande sur 53 bits); l'arrondi interne en simple précision est également supporté, mais n'est pas utilisé en pratique. Certains systèmes d'exploitation, comme FreeBSD, NetBSD jusqu'à la version 6 et Microsoft Windows (mais pas toujours?), ont choisi de configurer le processeur pour que, par défaut, l'arrondi se fasse en double précision. En revanche, sous GNU Linux, l'arrondi se fait en précision étendue (je ne connais pas d'autre système ayant le même comportement par défaut). C'est un mauvais choix pour les raisons données ci-dessous.
Note: les instructions SSE2, utilisées par les systèmes 64 bits en particulier, ne sont pas concernées par ces problèmes.
Des informations plus à jour peuvent être trouvées sur Wikipédia.
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 ISO.
Un certain nombre de personnes ont été confrontées à ces problèmes;
cf le
bug 323
de GCC 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.
Notez que le bug 323 de
GCC a été
corrigé dans
GCC 4.5, dès lors
que les bonnes options de compilation sont utilisées, comme demander
d'être conforme à la norme C (voir aussi le
patch
correspondant):
GCC now
supports handling floating-point excess precision arising from use of the
x87 floating-point unit in a way that conforms to ISO C99.
This is enabled with -fexcess-precision=standard and with
standards conformance options such as -std=c99, and may
be disabled using -fexcess-precision=fast.
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.3.3) 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), mais voir ci-dessous concernant Firefox. Bug 309797 sur le BTS de Debian.
Opera (dernière version testée: 11.00).
Le bug a apparemment été corrigé dans Firefox. La version 1.5 Gecko/20060110 donnait un résultat incorrect (idem jusqu'à Gecko 1.9.1), mais des versions récentes comme Firefox 3.5.6 et au-delà semblent se comporter correctement. Bug 264912 sur Bugzilla (maintenant fermé).
Un bug critique a été trouvé dans PHP, mais il est lié à la plage d'exposants plus grande (et le bug 323 de GCC), plutôt qu'à la précision étendue (le code en question tourne dans le mode double précision). Voir ci-dessous.
En fait, la configuration du FPU x87 en mode double précision ne résout pas tous les problèmes. En effet, les registres x87 ont aussi une plage d'exposants plus grande. Cela signifie que pour espérer être portable, on (généralement, le compilateur) devrait stocker la valeur en mémoire pour réellement obtenir un résultat dans le format double précision, mais avec une perte d'efficacité. Ensuite, une valeur dans la plage des dénormalisés du format double précision sera toujours sur 53 bits dans un registre x87 au lieu d'avoir une précision réduite, et un stockage de la valeur en mémoire va donner un double arrondi. Il est possible de traiter ces cas-là correctement, mais ce n'est plus aussi simple. Et bien que ces cas soient rares (ce sont uniquement des cas d'underflow), ils peuvent arriver en pratique, comme cela est montré ci-dessous.
Un bug dans PHP dû à la plage d'exposants plus grande a été trouvé par Rick Regan le 2010-12-30: PHP Hangs On Numeric Value 2.2250738585072011e-308. Voir aussi son rapport de bug et ses explications. En fait, le bug réel est le bug 323 de GCC, mais PHP doit l'éviter. Conséquence pour un serveur web: possible déni de service.
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 David Goldberg, avec l'addendum Differences Among IEEE 754 Implementations de Doug Priest (article en PDF, avec l'addendum).