English version

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

Introduction

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.

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

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

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:



webmaster@vinc17.org