Précision et précision dans les calculs à virgule flottante

Numéro de la base de connaissances d’origine : 125056

Résumé

Il existe de nombreuses situations dans lesquelles la précision, l’arrondi et la précision dans les calculs à virgule flottante peuvent fonctionner pour générer des résultats surprenants pour le programmeur. Ils doivent suivre les quatre règles générales :

  1. Dans un calcul impliquant à la fois une et une double précision, le résultat n’est généralement pas plus précis que la précision unique. Si la double précision est requise, assurez-vous que tous les termes du calcul, y compris les constantes, sont spécifiés en double précision.

  2. Ne supposez jamais qu’une valeur numérique simple est représentée avec précision sur l’ordinateur. La plupart des valeurs à virgule flottante ne peuvent pas être représentées avec précision sous la forme d’une valeur binaire finie. Par exemple, .1 est .0001100110011... en binaire (il se répète indéfiniment), il ne peut donc pas être représenté avec une précision complète sur un ordinateur à l’aide de l’arithmétique binaire, qui inclut tous les PC.

  3. Ne supposez jamais que le résultat est précis jusqu’à la dernière décimale. Il y a toujours de petites différences entre la réponse « vraie » et ce qui peut être calculé avec la précision finie de toute unité de traitement à virgule flottante.

  4. Ne comparez jamais deux valeurs à virgule flottante pour voir si elles sont égales ou non égales. Il s’agit d’un corollaire de la règle 3. Il y aura presque toujours de petites différences entre les nombres qui « devraient » être égaux. Au lieu de cela, case activée toujours pour voir si les nombres sont presque égaux. En d’autres termes, case activée de voir si la différence entre eux est faible ou insignifiante.

Informations supplémentaires

En général, les règles décrites ci-dessus s’appliquent à tous les langages, y compris C, C++ et assembleur. Les exemples ci-dessous illustrent certaines des règles utilisant FORTRAN PowerStation. Tous les exemples ont été compilés à l’aide de FORTRAN PowerStation 32 sans aucune option, à l’exception du dernier, qui est écrit en C.

Exemple 1

Le premier exemple illustre deux choses :

  • Que les constantes FORTRAN sont une précision unique par défaut (les constantes C sont une double précision par défaut).
  • Les calculs qui contiennent des termes de précision uniques ne sont pas beaucoup plus précis que les calculs dans lesquels tous les termes sont une précision unique.

Après avoir été initialisé avec 1.1 (constante de précision unique), y est aussi inexact qu’une variable de précision unique.

x = 1.100000000000000  y = 1.100000023841858

Le résultat de la multiplication d’une valeur de précision unique par une valeur de précision double précise est presque aussi mauvais que la multiplication de deux valeurs de précision uniques. Les deux calculs comportent des milliers de fois plus d’erreurs que la multiplication de deux valeurs de double précision.

true = 1.320000000000000 (multiplying 2 double precision values)
y    = 1.320000052452087 (multiplying a double and a single)
z    = 1.320000081062318 (multiplying 2 single precision values)

Exemple de code

C Compile options: none

       real*8 x,y,z
       x = 1.1D0
       y = 1.1
       print *, 'x =',x, 'y =', y
       y = 1.2 * x
       z = 1.2 * 1.1
       print *, x, y, z
       end

Exemple 2

L’exemple 2 utilise l’équation quadratique. Il montre que même les calculs de double précision ne sont pas parfaits et que le résultat d’un calcul doit être testé avant qu’il ne soit dépendant de si de petites erreurs peuvent avoir des résultats spectaculaires. L’entrée de la fonction racine carrée dans l’exemple 2 n’est que légèrement négative, mais elle n’est toujours pas valide. Si les calculs de double précision n’ont pas de légères erreurs, le résultat serait :

Root =   -1.1500000000

Au lieu de cela, il génère l’erreur suivante :

Erreur d’exécution M6201 : MATH

  • sqrt : erreur de domaine

Exemple de code

C Compile options: none

       real*8 a,b,c,x,y
       a=1.0D0
       b=2.3D0
       c=1.322D0
       x = b**2
       y = 4*a*c
       print *,x,y,x-y
       print "(' Root =',F16.10)",(-b+dsqrt(x-y))/(2*a)
       end

Exemple 3

L’exemple 3 montre qu’en raison des optimisations qui se produisent même si l’optimisation n’est pas activée, les valeurs peuvent conserver temporairement une précision plus élevée que prévu, et qu’il n’est pas judicieux de tester l’égalité de deux valeurs à virgule flottante.

Dans cet exemple, deux valeurs sont égales et non égales. Au premier IF, la valeur de Z est toujours sur la pile du coprocesseur et a la même précision que Y. Par conséquent, X n’est pas égal à Y et le premier message est imprimé. Au moment du deuxième SI, Z devait être chargé à partir de la mémoire et avait donc la même précision et la même valeur que X, et le deuxième message est également imprimé.

Exemple de code

C Compile options: none

       real*8 y
       y=27.1024D0
       x=27.1024
       z=y
       if (x.ne.z) then
         print *,'X does not equal Z'
       end if
       if (x.eq.z) then
         print *,'X equals Z'
       end if
       end

Exemple 4

La première partie de l’exemple de code 4 calcule la plus petite différence possible entre deux nombres proches de 1,0. Pour ce faire, il ajoute un seul bit à la représentation binaire de 1.0.

x   = 1.00000000000000000  (one bit more than 1.0)
y   = 1.00000000000000000  (exactly 1.0)
x-y =  .00000000000000022  (smallest possible difference)

Certaines versions de FORTRAN arrondissent les nombres lorsqu’ils sont affichés afin que l’imprécision numérique inhérente ne soit pas si évidente. C’est pourquoi x et y se présentent de la même façon lorsqu’ils sont affichés.

La deuxième partie de l’exemple de code 4 calcule la plus petite différence possible entre deux nombres proches de 10,0. Là encore, il le fait en ajoutant un bit unique à la représentation binaire de 10.0. Notez que la différence entre les nombres proches de 10 est supérieure à la différence proche de 1. Cela illustre le principe général selon lequel plus la valeur absolue d’un nombre est grande, moins il peut être stocké avec précision dans un nombre donné de bits.

x   = 10.00000000000000000  (one bit more than 10.0)
y   = 10.00000000000000000  (exactly 10.0)
x-y =   .00000000000000178

La représentation binaire de ces nombres est également affichée pour montrer qu’ils diffèrent d’un seul bit.

x = 4024000000000001 Hex
y = 4024000000000000 Hex

La dernière partie de l’exemple de code 4 montre que les valeurs décimales simples et non répétées peuvent souvent être représentées en binaire uniquement par une fraction répétée. Dans ce cas x=1.05, qui nécessite un facteur répétitif CCCCCCCC.... (Hexadécimal) dans la mantisse. Dans FORTRAN, le dernier chiffre « C » est arrondi à « D » afin de maintenir la plus grande précision possible :

x = 3FF0CCCCCCCCCCCD (Hex representation of 1.05D0)

Même après arrondi, le résultat n’est pas parfaitement précis. Il y a une erreur après le chiffre le moins significatif, que nous pouvons voir en supprimant le premier chiffre.

x-1 = .05000000000000004

Exemple de code

C Compile options: none

       IMPLICIT real*8 (A-Z)
       integer*4 i(2)
       real*8 x,y
       equivalence (i(1),x)

       x=1.
       y=x
       i(1)=i(1)+1
       print "(1x,'x  =',F20.17,'  y=',f20.17)", x,y
       print "(1x,'x-y=',F20.17)", x-y
       print *

       x=10.
       y=x
       i(1)=i(1)+1
       print "(1x,'x  =',F20.17,'  y=',f20.17)", x,y
       print "(1x,'x-y=',F20.17)", x-y
       print *
       print "(1x,'x  =',Z16,' Hex  y=',Z16,' Hex')", x,y
       print *

       x=1.05D0
       print "(1x,'x  =',F20.17)", x
       print "(1x,'x  =',Z16,' Hex')", x
       x=x-1
       print "(1x,'x-1=',F20.17)", x
       print *
       end

Exemple 5

En C, les constantes flottantes sont doubles par défaut. Utilisez un « f » pour indiquer une valeur float, comme dans « 89.95f ».

/* Compile options needed: none
*/ 

#include <stdio.h>

void main()
   {
      float floatvar;
      double doublevar;

/* Print double constant. */ 
      printf("89.95 = %f\n", 89.95);      // 89.95 = 89.950000

/* Printf float constant */ 
      printf("89.95 = %f\n", 89.95F);     // 89.95 = 89.949997

/*** Use double constant. ***/ 
      floatvar = 89.95;
      doublevar = 89.95;

printf("89.95 = %f\n", floatvar);   // 89.95 = 89.949997
      printf("89.95 = %lf\n", doublevar); // 89.95 = 89.950000

/*** Use float constant. ***/ 
      floatvar = 89.95f;
      doublevar = 89.95f;

printf("89.95 = %f\n", floatvar);   // 89.95 = 89.949997
      printf("89.95 = %lf\n", doublevar); // 89.95 = 89.949997
   }