Y a-t-il un problème avec les calculs en virgule flottante ?
P粉071602406
2023-08-20 16:48:26
<p>Considérez le code suivant : </p>
<pre class="brush:js;toolbar:false;">0,1 + 0,2 == 0,3 ->
≪/pré>
<pre class="brush:js;toolbar:false;">0,1 + 0,2 ->
≪/pré>
<p>Pourquoi ces inexactitudes se produisent-elles ? </p>
Point de vue du concepteur de matériel
J'ai pensé que je devrais ajouter le point de vue d'un concepteur de matériel, puisque je conçois et construis du matériel à virgule flottante. Connaître l'origine de l'erreur peut aider à comprendre ce qui se passe dans le logiciel et, en fin de compte, j'espère que cela expliquera pourquoi les erreurs en virgule flottante se produisent et semblent s'accumuler au fil du temps.
1. Aperçu
D'un point de vue technique, la plupart des opérations en virgule flottante comporteront des erreurs car le matériel effectuant les calculs en virgule flottante n'a besoin que de moins d'une demi-unité d'erreur dans le dernier bit. Par conséquent, la plupart des matériels ne s'arrêteront qu'à la précision et n'auront besoin de produire qu'une erreur inférieure à la moitié de l'unité du dernier bit, ce qui est particulièrement problématique dans la division en virgule flottante. Ce qui constitue une opération unique dépend du nombre d'opérandes acceptés par l'unité. Pour la plupart des unités, il s'agit de deux opérandes, mais certaines unités acceptent 3 opérandes ou plus. Par conséquent, il n’y a aucune garantie que des opérations répétées produiront des erreurs idéales, car les erreurs s’accumulent au fil du temps.
2. Norme
La plupart des processeurs suivent la norme IEEE-754, mais certains utilisent des normes dénormalisées ou différentes. Par exemple, il existe un mode de dénormalisation dans IEEE-754 qui permet la représentation de très petits nombres à virgule flottante au détriment de la précision. Cependant, le contenu suivant couvrira le mode normalisé IEEE-754, qui est le mode de fonctionnement typique.
Dans la norme IEEE-754, les concepteurs de matériel peuvent autoriser n'importe quelle valeur d'erreur/ε tant qu'elle est inférieure à la moitié d'une unité du dernier bit, et le résultat doit être inférieur à la moitié d'une unité du dernier bit seulement pour un opération. Ceci explique pourquoi les erreurs s'accumulent lorsque les opérations sont répétées. Pour la double précision IEEE-754, il s'agit du bit 54 car 53 bits sont utilisés pour représenter la partie numérique (normalisation) du nombre à virgule flottante, également appelée mantisse (par exemple 5.3 en 5.3e5). Les sections suivantes traitent plus en détail des causes des erreurs matérielles dans diverses opérations à virgule flottante.
3. Causes d'erreur d'arrondi dans la division
La principale cause d'erreur dans la division en virgule flottante est l'algorithme de division utilisé pour calculer le quotient. La plupart des systèmes informatiques utilisent l'inverse multiplicatif pour les calculs de division, principalement dans le bit
Z=X/Y
和Z = X * (1/Y)
中。除法是迭代计算的,即每个周期计算一些商的位数,直到达到所需的精度,对于IEEE-754来说,这是任何误差小于最后一位的一个单位的情况。Y的倒数表(1/Y)被称为慢除法中的商选择表(QST),商选择表的位数通常是基数的宽度,或者在每次迭代中计算的商的位数加上几个保护位。对于IEEE-754标准的双精度(64位),它将是除法器的基数大小加上几个保护位k,其中k>=2
。因此,例如,一个典型的每次计算2位商(基数为4)的除法器的商选择表将是2+2= 4
(plus quelques bits facultatifs).3.1 Erreur d'arrondi de division : approximation de l'inverse
Les réciproques dans le tableau de sélection des quotients dépendent de la méthode de division : division lente (comme la division SRT) ou division rapide (comme la division Goldschmidt) ; chaque entrée est modifiée selon l'algorithme de division pour produire l'erreur la plus faible possible. Quoi qu'il en soit, toutes les réciproques sont des approximations des réciproques réelles et introduisent un certain degré d'erreur. Les méthodes de division lente et rapide calculent le quotient de manière itérative, c'est-à-dire qu'un certain nombre de chiffres du quotient sont calculés à chaque étape, le résultat est ensuite soustrait du dividende et le diviseur répète ces étapes jusqu'à ce que l'erreur soit inférieure à une demi-unité du dernier chiffre. Les méthodes de division lente calculent un nombre fixe de chiffres du quotient à chaque étape et sont généralement moins chères, tandis que les méthodes de division rapide calculent un nombre variable de chiffres du quotient à chaque étape et sont généralement plus coûteuses. La partie la plus importante des méthodes de division est que la plupart d'entre elles reposent sur des multiplications répétées de approximations par rapport aux réciproques et sont donc sujettes aux erreurs.
4. Erreurs d'arrondi dans d'autres opérations : troncature
Une autre cause d'erreurs d'arrondi dans toutes les opérations est les différents modes de troncature autorisés par IEEE-754. Il y a tronqué, arrondi vers zéro, arrondi (par défaut) , arrondi vers le bas et arrondi vers le haut. Toutes les méthodes introduisent une erreur inférieure à l’unité du dernier bit, pour une seule opération. Au fil du temps et des opérations répétées, la troncature s'accumule également dans l'erreur qui en résulte. Cette erreur de troncature est particulièrement problématique en arithmétique exponentielle, qui implique une certaine forme de multiplication répétée.
5. Répétez l'opération
Étant donné que le matériel effectuant des calculs en virgule flottante n'a besoin de produire qu'un résultat en une seule opération inférieur à une demi-unité d'erreur dans le dernier bit, s'il n'est pas surveillé, l'erreur augmentera à mesure que l'opération est répétée. C'est pourquoi, dans les calculs qui nécessitent des erreurs limitées, les mathématiciens utilisent des méthodes telles que l'arrondi un nombre pair de derniers chiffres dans IEEE-754, car les erreurs sont plus susceptibles de s'annuler avec le temps, ainsi que la combinaison des changements dans Arithmétique d'intervalle et Mode d'arrondi IEEE 754 pour prédire les erreurs d'arrondi et les corriger. L'arrondi au chiffre pair le plus proche (dernier chiffre) est le mode d'arrondi par défaut de la norme IEEE-754 en raison d'une erreur relative plus faible par rapport aux autres modes d'arrondi.
Veuillez noter que le mode d'arrondi par défaut, arrondi au chiffre pair le plus proche, garantit une opération avec une erreur inférieure à la demi-unité du dernier chiffre. Utilisez uniquement tronquer, arrondir vers le haut et vers le bas
BinairePoint flottantC'est ainsi que fonctionnent les mathématiques. Dans la plupart des langages de programmation, il est basé sur la norme IEEE 754. Le nœud du problème est que les nombres sont représentés dans ce format comme un nombre entier multiplié par une puissance de deux ; les nombres rationnels dont les dénominateurs ne sont pas des puissances de deux (comme
0.1
,即1/10
) ne peuvent pas être représentés exactement.Pour le standard
binary64
格式中的0.1
, sa représentation peut s'écrire exactement comme0.1000000000000000055511151231257827021181583404541015625
0x1.999999999999ap-4
En revanche, les nombres rationnels
0.1
,即1/10
peuvent s'écrire exactement comme0.1
0x1.99999999999999...p-4
,其中...
Représente une séquence infinie de 9.Les constantes
0.2
et0.3
de votre programme se rapprocheront également de leurs vraies valeurs. Il se trouve que ledouble
le plus proche de0.2
est plus grand que le nombre rationnel0.2
, mais ledouble
le plus proche à0.3
code>est inférieur à un nombre rationnel0.3
. La somme de0.2
和0.3
也将近似于它们的真实值。恰好,最接近0.2
的double
大于有理数0.2
,但最接近0.3
的double
小于有理数0.3
。0.1
和0.2
的和最终比有理数0.3
et0,2
finit par être supérieure au nombre rationnel0,3
et est donc incompatible avec la constante du code.Un traitement assez complet des problèmes d'arithmétique à virgule flottante est Ce que tout informaticien devrait savoir sur l'arithmétique à virgule flottante. Pour une explication plus compréhensible, voir floating-point-gui.de.
Le même problème existe avec les nombres décimaux réguliers (base 10), c'est pourquoi un nombre comme 1/3 finit par être 0,333333333...
Vous venez de tomber sur un nombre (3/10) qui est facilement représentable dans le système décimal, mais ne peut pas être représenté dans le système binaire. L'inverse est également vrai (dans une certaine mesure) : 1/16 est un nombre laid en décimal (0,0625), mais en binaire, il semble aussi net qu'un dix millième en décimal (0,0001)* * - Si nous avions l'habitude d'utiliser le système de nombres binaires dans notre vie quotidienne, vous regardez même ce nombre et comprenez instinctivement que vous pouvez l'obtenir en le divisant constamment par 2.
Bien sûr, ce n’est pas ainsi que les nombres à virgule flottante sont stockés en mémoire (ils utilisent une forme de notation scientifique). Cela illustre cependant que des erreurs de précision binaires à virgule flottante ont tendance à survenir parce que les nombres du « monde réel » qui nous intéressent habituellement ont tendance à être des puissances de dix – mais c'est uniquement parce que nous utilisons le système de nombres décimaux au quotidien. base de jour. C'est pourquoi nous disons 71 % au lieu de « 5 sur 7 » (71 % est une approximation car 5/7 ne peut être représenté exactement par aucun nombre décimal).
Donc non, il n'y a rien de mal avec les nombres binaires à virgule flottante, ils sont juste imparfaits comme tout autre système numérique de base N :)
En pratique, ce problème de précision signifie que vous devez utiliser des fonctions d'arrondi pour arrondir les nombres à virgule flottante au nombre de décimales qui vous intéresse avant de les afficher.
Il faut également remplacer le test du signe égal par une comparaison qui autorise une certaine tolérance, ce qui signifie :
Ne pas utiliser
if (x == y) { ... }
Utilisez plutôt
if (abs(x - y) < myToleranceValue) { ... }
.Lequel
abs
是绝对值函数。myToleranceValue
doit être choisi en fonction de votre application spécifique, cela a beaucoup à voir avec la « marge de manœuvre » que vous êtes prêt à accorder et le nombre maximum que vous allez comparer (en raison de problèmes de perte de précision). Soyez conscient des constantes de style "epsilon" dans la langue de votre choix. Ces constantes peuvent être utilisées comme valeurs de tolérance, mais leur efficacité dépend de la taille des nombres que vous traitez, car les calculs avec de grands nombres peuvent dépasser le seuil epsilon.