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.pasconverti caos.pas en coas.c
cc caos.c -lp2c -o caoscompile 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.