Voici ce qui se passe en binaire. Comme nous le savons, certaines valeurs à virgule flottante ne peuvent pas être représentées exactement en binaire, même si elles peuvent être représentées exactement en décimal. Ces 3 chiffres ne sont que des exemples de ce fait.
Avec ce programme, je produis les représentations hexadécimales de chaque nombre et les résultats de chaque addition.
public class Main{
public static void main(String args[]) {
double x = 23.53; // Inexact representation
double y = 5.88; // Inexact representation
double z = 17.64; // Inexact representation
double s = 47.05; // What math tells us the sum should be; still inexact
printValueAndInHex(x);
printValueAndInHex(y);
printValueAndInHex(z);
printValueAndInHex(s);
System.out.println("--------");
double t1 = x + y;
printValueAndInHex(t1);
t1 = t1 + z;
printValueAndInHex(t1);
System.out.println("--------");
double t2 = x + z;
printValueAndInHex(t2);
t2 = t2 + y;
printValueAndInHex(t2);
}
private static void printValueAndInHex(double d)
{
System.out.println(Long.toHexString(Double.doubleToLongBits(d)) + ": " + d);
}
}
le printValueAndInHex
méthode est juste une aide d'imprimante hexadécimale.
La sortie est la suivante:
403787ae147ae148: 23.53
4017851eb851eb85: 5.88
4031a3d70a3d70a4: 17.64
4047866666666666: 47.05
--------
403d68f5c28f5c29: 29.41
4047866666666666: 47.05
--------
404495c28f5c28f6: 41.17
4047866666666667: 47.050000000000004
Les 4 premiers chiffres sont x
, y
, z
et s
de » représentations hexadécimaux. Dans la représentation en virgule flottante IEEE, les bits 2 à 12 représentent l' exposant binaire , c'est-à-dire l'échelle du nombre. (Le premier bit est le bit de signe, et les bits restants pour la mantisse .) L'exposant représenté est en fait le nombre binaire moins 1023.
Les exposants des 4 premiers nombres sont extraits:
sign|exponent
403 => 0|100 0000 0011| => 1027 - 1023 = 4
401 => 0|100 0000 0001| => 1025 - 1023 = 2
403 => 0|100 0000 0011| => 1027 - 1023 = 4
404 => 0|100 0000 0100| => 1028 - 1023 = 5
Première série d'ajouts
Le deuxième nombre ( y
) est de plus petite ampleur. Lors de l'ajout de ces deux nombres à obtenir x + y
, les 2 derniers bits du deuxième nombre ( 01
) sont décalés hors de la plage et ne figurent pas dans le calcul.
Le deuxième ajout ajoute x + y
et z
ajoute deux nombres de la même échelle.
Deuxième série d'ajouts
Ici, x + z
se produit en premier. Ils sont de la même échelle, mais ils donnent un nombre plus élevé dans l'échelle:
404 => 0|100 0000 0100| => 1028 - 1023 = 5
Le deuxième ajout ajoute x + z
et y
, et maintenant 3 bits sont supprimés y
pour ajouter les nombres ( 101
). Ici, il doit y avoir un arrondi vers le haut, car le résultat est le prochain nombre à virgule flottante vers le haut: 4047866666666666
pour le premier ensemble d'additions par rapport à4047866666666667
au deuxième ensemble d'additions. Cette erreur est suffisamment importante pour apparaître dans l'impression du total.
En conclusion, soyez prudent lorsque vous effectuez des opérations mathématiques sur des nombres IEEE. Certaines représentations sont inexactes, et elles deviennent encore plus inexactes lorsque les échelles sont différentes. Ajoutez et soustrayez des nombres d'échelle similaire si vous le pouvez.
(2.0^53 + 1) - 1 == 2.0^53 - 1 != 2^53 == 2^53 + (1 - 1)
). Par conséquent, oui: méfiez-vous lors du choix de l'ordre des sommes et des autres opérations. Certains langages proposent une fonction intégrée pour effectuer des sommes «de haute précision» (par exemple, les pythonsmath.fsum
), vous pouvez donc envisager d'utiliser ces fonctions au lieu de l'algorithme de somme naïve.