Par Alan Ward
award@mypic.ad
Dans cet article je vous propose une découverte expérimentale du comportement de Linux quand il effectue des calculs en simple ou double précision. J'utilise une fonction chaotique pour montrer combien peut varier le calcul résultant d'un même programme selon qu'il est exécuté sous Linux ou sous un système d'exploitation de Microsoft.
Les publics concernés sont les étudiants en math et physique ainsi que les professeurs, bien que les équations mises en jeu soient accessibles à pratiquement tout le monde.
J'utilise les langages Pascal, C et Java puisque ce sont ceux-là qui sont le plus répandus de nos jours.
Ce papier s'intéresse plus particulièrement aux architectures Intel. Les concepts de base sont les mêmes pour d'autres types de processeurs bien que certains détails puissent changer.
Note du traducteur :
Le SGML, à ma connaissance, ne permet pas les indices/exposants.
Dans ce qui suit un indice sera symbolisé par un `_'
.
Donc, x indice k sera symbolisé par x_k
.
Ces fonctions construisent une série de termes de la forme :
x_0 appartient à [0,1]
x_(k+1) = mu . x_k . (1 - x_k) où mu est un paramêtre
i <= mu < 3
, le comportement des séries est
déterministe.3 <= mu < 4
, le comportement est chaotique.En simplifiant un peu, la différence entre un système chaotique et déterministe
est leur sensibilité aux conditions initiales.
Un système chaotique est très sensible : une petite variation des valeurs de
départ de x_0
conduira a des différences croissantes dans les termes suivants.
Donc, n'importe quelle erreur qui rôde dans les calculs -- comme un manque de
précision -- donnera éventuellement naissance à des résultats très différents.
D'autres exemples de systèmes chaotiques sont les orbites des satellites et les prévisions météorologiques.
D'un autre côté, un système déterministe n'est pas si sensible.
Une petite erreur dans x_0
nous ammènera à calculer des termes qui, bien
que différents des valeurs exactes, en seront "suffisamment approchés" (quoi que
cela puisse signifier).
Un exemple de système déterministe est la trajectoire d'une balle de ping-pong.
Donc, les fonctions chaotiques sont utiles pour tester la précision des calculs de différents systèmes et de différents compilateurs.
Dans cet exemple je propose d'utiliser les valeurs suivantes :
mu = 3,8
x_0 = 0,5
Un calcul précis avec un module spécial permettant une précision de 1000 chiffres donne les résultats suivants :
k x(k) ----- --------- 10 0.18509 20 0.23963 30 0.90200 40 0.82492 50 0.53713 60 0.66878 70 0.53202 80 0.93275 90 0.79885 100 0.23161
Comme vous pouvez le voir, les séries fluctuent joyeusement entre 0 et 1.
Un programme pour calculer cette fonction s'écrit simplement en Turbo PAscal pour MS-DOS : version texte
program caos; {$n+} { vous devez activer le calcul materiel des flottants pour pouvoir utiliser le type etendu } uses crt; var s : single; { 32-bit real } r : real; { 48-bit real } d : double; { 64-bit real } e : extended; { 80-bit real } i : integer; begin clrscr; s := 0.5; r := 0.5; d := 0.5; e := 0.5; for i := 1 to 100 do begin s := 3.8 * s * (1 - s); r := 3.8 * r * (1 - r); d := 3.8 * d * (1 - d); e := 3.8 * e * (1 - e); if (i/10 = int(i/10)) then begin writeln (i:10, s:16:5, r:16:5, d:16:5, e:16:5); end; end; readln; end.
Comme vous pouvez le voir, Turbo Pascal a un certain nombre de types de flottants, chacun sur un nombre différent de bits. Dans chaque cas des bits sont réservés pour :
Par exemple, sur un 386 un flottant de 80 bits est codé par :
Naturellement, le codage des flottants à l'intérieur de la machine est déterminé par le fabricant. Cependant, le compilateur peut spécifier des codages différents pour des calculs internes. Si l'émulation des flottants n'est pas utilisée, le compilateur doit fournir un moyen de transformer les codages du compilateur au matériel. C'est le cas du Turbo Pascal.
Les résultats du programme ci-dessus sont :
k single real double extended ---- --------- --------- --------- ---------- 10 0.18510 0.18510 0.18510 0.18510 20 0.23951 0.23963 0.23963 0.23963 30 0.88423 0.90200 0.90200 0.90200 40 0.23013 0.82492 0.82493 0.82493 50 0.76654 0.53751 0.53714 0.53714 60 0.42039 0.64771 0.66878 0.66879 70 0.93075 0.57290 0.53190 0.53203 80 0.28754 0.72695 0.93557 0.93275 90 0.82584 0.39954 0.69203 0.79884 100 0.38775 0.48231 0.41983 0.23138
Dans tous les cas, les premiers termes sont assez proches, étant donné que les pertes
de précision (par troncature) dues aux calculs intenses n'ont jamais eu lieu.
Il en résulte que le format le moins précis (single) perd tout contact avec la
réalité aux alentours de x_30
, alors que le format réel s'en tire jusqu'à
environ x_60
et que le format double n'est pas encore complètement faux à
x_90
.
Ce sont tous des codages flottants du compilateur.
Le format étendu -- qui est le format de codage natif du matériel -- garde une
précision suffisante jusqu'à x_100
.
L'expérience me fait dire qu'il va probablement diverger aux alentours de
x_110
.
Le programme ci-dessus peut-être compilé pratiquement sans changement en
utilisant le programme de traduction p2c
disponible sous Linux :
p2c caos.pas
converti caos.pas en coas.c
cc caos.c -lp2c -o caos
compile coas.c avec la bibliothèque p2c en utilisant gcc
Les résultats deviennent :
k single real double extended ---- --------- --------- --------- ---------- 10 0.18510 0.18510 0.18510 0.18510 20 0.23951 0.23963 0.23963 0.23963 30 0.88423 0.90200 0.90200 0.90200 40 0.23013 0.82493 0.82493 0.82493 50 0.76654 0.53714 0.53714 0.53714 60 0.42039 0.66878 0.66878 0.66878 70 0.93075 0.53190 0.53190 0.53190 80 0.28754 0.93558 0.93558 0.93558 90 0.82584 0.69174 0.69174 0.69174 100 0.38775 0.49565 0.49565 0.49565
Il est intéressant de noter que le traducteur p2c
convertit les nombres
flottants simple précision du Pascal en simple précision du C, alors que les
types real
, double
et extended
sont tous transformés en
type double
du C.
C'est le format qui garde sa précision jusqu'aux alentours de x_80
.
Je ne dispose pas de données pour prouver le bien fondé de ce qui suit, mais mon
impression est que le type C double
de codage des flottants se fait également
sur 64 bits, mais avec une répartition mantisse/exposant différente de celle du
Turbo Pascal.
gcc
sous LinuxLe programme ci-dessus,
réécrit en C et compilé
avec gcc
donne, de manière assez naturelle, exactement les mêmes résultats
qu'avec p2c
:
#include <stdio.h> int main() { float f; double d; int i; f = 0.5; d = 0.5; for (i = 1; i <= 100; i++) { f = 3.8 * f * (1 - f); d = 3.8 * d * (1 - d); if (i % 10 == 0) printf ("%10d %20.5f %20.5f\n", i, f, d); } }
Le langage de programmation Java est un cas complètement à part, étant donné que, dès le départ, il a été conçu pour fonctionner sur des plateformes aussi nombreuses que différentes.
Un fichier Java .class
contient le code source du programme compilé dans le langage
d'une Machine Virtuelle.
Ce fichier ``exécutable'' est alors interprété sur un système client par le
premier interprête Java disponible.
Cependant, dans les spécifications Java, les problèmes de précision sur les flottants sont pris très au sérieux. Chaque interprête Java devrait obtenir exactement les mêmes résultats sur des calculs en simple ou double précision.
Ceci signifie qu'un même programme devra :
Le lecteur peut aisément expérimenter cela. L'applet suivante calcule les séries de May dont nous avons parlé. Comparez ses résultats sur votre propre configuration en l'exécutant avec Netscape, HotJava, appletviewer, etc... Vous pouvez également comparez avec les mêmes navigateurs, ou d'autres, sous Windoze. Ouvrez simplement cette page avec chaque navigateur.
Jusqu'à présent je n'ai trouvé qu'une seule exception à cette règle. Vous devinez qui ? Microsoft Explorer 3.0 !
Pour terminer, voilà le code source de l'applet Java :
import java.applet.Applet; import java.lang.String; import java.awt.*; public class caos extends Applet { public void paint (Graphics g) { float f; double d; String s; int i, y; f = (float)0.5; d = 0.5; g.setColor (Color.black); g.drawString ("k", 10, 10); g.drawString ("float", 50, 10); g.drawString ("double", 150, 10); g.setColor (Color.red); y = 20; for (i = 1; i <= 100; i++) { f = (float)3.8* f * ((float)1.0 - f); d = 3.8 * d * (1.0 - d); if (i % 10 == 0) { y += 12; g.drawString (java.lang.String.valueOf(i), 10, y); g.drawString (java.lang.String.valueOf(f), 50, y); g.drawString (java.lang.String.valueOf(d), 150, y); } } } }
An introduction to Chaotic Dynamical Systems, R.L. Devaney
Jurassic Park I and II, Michael Crichton (les livres, pas les
films !)
The Intel 386-SX microprocessor data sheet, Intel Corp. (disponible à
http://developer.intel.com).
Copyright 1999, Alan Ward. Paru dans le numéro 53 de la Linux Gazette de Mai 2000.
Traduction française de Xavier Serpaggi.