English version

GNU Linux et la précision étendue sur les processeurs x86

[Document initialement écrit en 2004.]

Introduction

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.

But de la précision étendue

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.

Le double arrondi

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é.

Problèmes de portabilité

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.

Problèmes de précision

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.

Problèmes avec certains algorithmes

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.

Problèmes liés au respect des normes

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.

Bugs liés au choix de la précision étendue par défaut

Processeurs XSLT

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:

Machines virtuelles Java (JVM)

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:

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.

Javascript (ECMAScript)

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é).

PHP

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.

Problèmes dus à la plage d'exposants plus grande

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.



webmaster@vinc17.org