Les mathématiques en virgule flottante sont-elles cassées?

2983

Considérez le code suivant:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

Pourquoi ces inexactitudes se produisent-elles?

Cato Johnston
la source
127
Les variables à virgule flottante ont généralement ce comportement. Cela est dû à la façon dont ils sont stockés dans le matériel. Pour plus d'informations, consultez l'article Wikipedia sur les nombres à virgule flottante .
Ben S
62
JavaScript traite les décimales comme des nombres à virgule flottante , ce qui signifie que des opérations telles que l'ajout peuvent être sujettes à une erreur d'arrondi. Vous voudrez peut-être lire cet article: Ce que tout informaticien devrait savoir sur l'arithmétique
mat b
4
Juste pour information, TOUS les types numériques en javascript sont des doubles IEEE-754.
Gary Willoughby
6
Étant donné que JavaScript utilise la norme IEEE 754 pour les mathématiques, il utilise des nombres flottants 64 bits . Cela provoque des erreurs de précision lors des calculs en virgule flottante (décimal), en bref, en raison des ordinateurs travaillant en Base 2 alors que la décimale est en Base 10 .
Pardeep Jain

Réponses:

2253

Les mathématiques en virgule flottante binaire sont comme ça. 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 fois une puissance de deux; les nombres rationnels (comme 0.1, qui est 1/10) dont le dénominateur n'est pas une puissance de deux ne peuvent pas être représentés exactement.

Pour 0.1dans le binary64format standard , la représentation peut être écrite exactement comme

  • 0.1000000000000000055511151231257827021181583404541015625 en décimal, ou
  • 0x1.999999999999ap-4en notation C99 hexfloat .

En revanche, le nombre rationnel 0.1, qui est 1/10, peut être écrit exactement comme

  • 0.1 en décimal, ou
  • 0x1.99999999999999...p-4dans un analogue de la notation hexfloat C99, où le ...représente une séquence sans fin de 9.

Les constantes 0.2et 0.3dans votre programme seront également des approximations de leurs vraies valeurs. Il arrive que le plus proche doublede 0.2soit plus grand que le nombre rationnel 0.2mais que le plus proche doublede 0.3soit plus petit que le nombre rationnel 0.3. La somme de 0.1et 0.2finit par être supérieure au nombre rationnel 0.3et donc en désaccord avec la constante de votre code.

Un traitement assez complet des problèmes d'arithmétique en virgule flottante est ce que tout informaticien devrait savoir sur l' arithmétique en virgule flottante . Pour une explication plus facile à digérer, voir flottant-point-gui.de .

Note latérale: Tous les systèmes numériques positionnels (base-N) partagent ce problème avec précision

Les anciens nombres décimaux (base 10) ont les mêmes problèmes, c'est pourquoi des nombres comme 1/3 finissent par 0,333333333 ...

Vous venez de tomber sur un nombre (3/10) qui se révèle facile à représenter avec le système décimal, mais ne correspond pas au système binaire. Cela va dans les deux sens (dans une certaine mesure): 1/16 est un nombre laid en décimal (0,0625), mais en binaire, il semble aussi net qu'un 10 000e en décimal (0,0001) ** - si nous étions en l'habitude d'utiliser un système de nombres de base 2 dans notre vie quotidienne, vous regarderiez même ce nombre et comprendriez instinctivement que vous pourriez y arriver en réduisant de moitié quelque chose, en le réduisant encore et encore, encore et encore.

** Bien sûr, ce n'est pas exactement la façon dont les nombres à virgule flottante sont stockés en mémoire (ils utilisent une forme de notation scientifique). Cependant, cela illustre le fait que les erreurs de précision en virgule flottante binaire ont tendance à apparaître parce que les nombres "réels" avec lesquels nous sommes généralement intéressés de travailler sont si souvent des puissances de dix - mais uniquement parce que nous utilisons un système de nombres décimaux jour- aujourd'hui. C'est aussi pourquoi nous dirons des choses comme 71% au lieu de "5 sur 7" (71% est une approximation, car 5/7 ne peut pas être représenté exactement avec un nombre décimal).

Donc non: les nombres à virgule flottante binaires ne sont pas cassés, ils se trouvent être aussi imparfaits que tous les autres systèmes de nombres en base-N :)

Side Side Note: Travailler avec des flottants dans la programmation

Dans la pratique, ce problème de précision signifie que vous devez utiliser des fonctions d'arrondi pour arrondir vos nombres à virgule flottante au nombre de décimales qui vous intéresse avant de les afficher.

Vous devez également remplacer les tests d'égalité par des comparaisons qui permettent une certaine tolérance, ce qui signifie:

Ne fais pasif (x == y) { ... }

Au lieu de cela if (abs(x - y) < myToleranceValue) { ... }.

absest la valeur absolue. myToleranceValuedoit être choisi pour votre application particulière - et cela aura beaucoup à voir avec la quantité de "marge de manœuvre" que vous êtes prêt à autoriser, et quel peut être le plus grand nombre que vous allez comparer (en raison de problèmes de perte de précision) ). Méfiez-vous des constantes de style "epsilon" dans la langue de votre choix. Ces valeurs ne doivent pas être utilisées comme valeurs de tolérance.

Daniel Scott
la source
181
Je pense que "une certaine constante d'erreur" est plus correcte que "L'Epsilon" car il n'y a pas "L'Epsilon" qui pourrait être utilisé dans tous les cas. Différents epsilons doivent être utilisés dans différentes situations. Et la machine epsilon n'est presque jamais une bonne constante à utiliser.
Rotsor
34
Ce n'est pas tout à fait vrai que toutes les mathématiques à virgule flottante sont basées sur la norme IEEE [754]. Il existe encore certains systèmes qui utilisent l'ancien FP hexadécimal IBM, par exemple, et il existe encore des cartes graphiques qui ne prennent pas en charge l'arithmétique IEEE-754. Cela est cependant fidèle à une approximation raisonnable.
Stephen Canon
19
Cray a abandonné la conformité IEEE-754 pour la vitesse. Java a également assoupli son adhésion en tant qu'optimisation.
Art Taylor
28
Je pense que vous devriez ajouter quelque chose à cette réponse sur la façon dont les calculs sur l'argent devraient toujours, toujours être effectués avec une arithmétique à virgule fixe sur des nombres entiers , car l'argent est quantifié. (Il peut être judicieux d'effectuer des calculs comptables internes en infimes fractions de cent, ou quelle que soit votre plus petite unité monétaire - cela aide souvent à réduire, par exemple, les erreurs d'arrondi lors de la conversion de "29,99 $ par mois" en taux quotidien - mais cela devrait être encore arithmétique à virgule fixe.)
zwol
18
Fait intéressant: ce très 0,1 n'étant pas exactement représenté en virgule flottante binaire a provoqué un infâme bug de logiciel de missile Patriot qui a fait 28 morts pendant la première guerre en Irak.
hdl
603

Point de vue d'un concepteur de matériel

Je crois que je devrais ajouter une perspective de concepteur de matériel à cela puisque je conçois et fabrique du matériel à virgule flottante. Connaître l'origine de l'erreur peut aider à comprendre ce qui se passe dans le logiciel et, finalement, j'espère que cela aidera à expliquer les raisons pour lesquelles les erreurs à virgule flottante se produisent et semblent s'accumuler au fil du temps.

1. Vue d'ensemble

D'un point de vue technique, la plupart des opérations en virgule flottante comporteront un élément d'erreur, car le matériel qui effectue les calculs en virgule flottante ne doit avoir en dernier lieu qu'une erreur de moins de la moitié d'une unité. Par conséquent, une grande partie du matériel s'arrêtera à une précision qui n'est nécessaire que pour produire une erreur de moins de la moitié d'une unité en dernier lieu pour une seule opération, ce qui est particulièrement problématique dans la division en virgule flottante. Ce qui constitue une seule opération dépend du nombre d'opérandes que l'unité prend. Pour la plupart, c'est deux, mais certaines unités prennent 3 opérandes ou plus. Pour cette raison, il n'y a aucune garantie que des opérations répétées entraîneront une erreur souhaitable car les erreurs s'ajoutent au fil du temps.

2. Normes

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 dénormalisé dans IEEE-754 qui permet la représentation de très petits nombres à virgule flottante au détriment de la précision. Cependant, ce qui suit couvrira le mode normalisé de l'IEEE-754 qui est le mode de fonctionnement typique.

Dans la norme IEEE-754, les concepteurs de matériel sont autorisés à toute valeur d'erreur / epsilon tant qu'il est inférieur à la moitié d'une unité à la dernière place, et que le résultat ne doit être inférieur à la moitié d'une unité que dans la dernière place pour une opération. Cela explique pourquoi lorsqu'il y a des opérations répétées, les erreurs s'additionnent. Pour la double précision IEEE-754, il s'agit du 54e bit, car 53 bits sont utilisés pour représenter la partie numérique (normalisée), également appelée mantisse, du nombre à virgule flottante (par exemple, le 5,3 en 5,3e5). Les sections suivantes détaillent les causes des erreurs matérielles sur diverses opérations en virgule flottante.

3. Cause de l'erreur d'arrondi dans la division

La principale cause de l'erreur dans la division en virgule flottante est les algorithmes de division utilisés pour calculer le quotient. La plupart des systèmes informatiques calculent la division en utilisant la multiplication par un inverse, principalement dans Z=X/Y,Z = X * (1/Y). Une division est calculée de manière itérative, c'est-à-dire que chaque cycle calcule quelques bits du quotient jusqu'à ce que la précision souhaitée soit atteinte, ce qui pour IEEE-754 est n'importe quoi avec une erreur de moins d'une unité en dernier lieu. La table des inverses de Y (1 / Y) est connue sous le nom de table de sélection de quotient (QST) dans la division lente, et la taille en bits de la table de sélection de quotient est généralement la largeur du radix, ou un nombre de bits de le quotient calculé à chaque itération, plus quelques bits de garde. Pour la norme IEEE-754, double précision (64 bits), ce serait la taille du radix du diviseur, plus quelques bits de garde k, où k>=2. Ainsi, par exemple, un tableau de sélection de quotient typique pour un diviseur qui calcule 2 bits du quotient à la fois (radix 4) serait des 2+2= 4bits (plus quelques bits facultatifs).

3.1 Erreur d'arrondi de division: approximation de la réciprocité

Les inverses dans le tableau de sélection des quotients dépendent de la méthode de division : division lente telle que la division SRT ou division rapide telle que la division Goldschmidt; chaque entrée est modifiée selon l'algorithme de division pour tenter de générer l'erreur la plus faible possible. Dans tous les cas, cependant, tous les réciproques sont des approximationsde la réciproque réelle et introduire un élément d'erreur. Les méthodes de division lente et de division rapide calculent le quotient de manière itérative, c'est-à-dire qu'un certain nombre de bits du quotient sont calculés à chaque étape, puis le résultat est soustrait du dividende et le diviseur répète les étapes jusqu'à ce que l'erreur soit inférieure à la moitié d'un l'unité en dernier lieu. Les méthodes de division lente calculent un nombre fixe de chiffres du quotient à chaque étape et sont généralement moins coûteuses à construire, et les méthodes de division rapide calculent un nombre variable de chiffres par étape et sont généralement plus coûteuses à construire. La partie la plus importante des méthodes de division est que la plupart d'entre elles reposent sur une multiplication répétée par une approximation d'une réciproque, de sorte qu'elles sont sujettes à l'erreur.

4. Erreurs d'arrondi dans d'autres opérations: troncature

Une autre cause des erreurs d'arrondi dans toutes les opérations est les différents modes de troncature de la réponse finale que permet IEEE-754. Il y a tronqué, arrondi à zéro, arrondi au plus proche (par défaut), arrondi et arrondi. Toutes les méthodes introduisent en dernier lieu un élément d'erreur inférieur à une unité pour une seule opération. Au fil du temps et des opérations répétées, la troncature ajoute également cumulativement à l'erreur résultante. Cette erreur de troncature est particulièrement problématique dans l'exponentiation, qui implique une certaine forme de multiplication répétée.

5. Opérations répétées

Étant donné que le matériel qui effectue les calculs en virgule flottante n'a besoin que de produire un résultat avec une erreur inférieure à la moitié d'une unité en dernier lieu pour une seule opération, l'erreur augmentera au fil des opérations répétées si elle n'est pas surveillée. C'est la raison pour laquelle dans les calculs qui nécessitent une erreur bornée, les mathématiciens utilisent des méthodes telles que l'utilisation du chiffre pair arrondi au plus proche à la dernière place de IEEE-754, car, au fil du temps, les erreurs sont plus susceptibles de s'annuler out, et Arithmetic Interval combiné avec des variations des modes d'arrondi IEEE 754pour prévoir les erreurs d'arrondi et les corriger. En raison de sa faible erreur relative par rapport aux autres modes d'arrondi, l'arrondi au chiffre pair le plus proche (en dernier lieu) est le mode d'arrondi par défaut de l'IEEE-754.

Notez que le mode d'arrondi par défaut, arrondi au chiffre pair le plus proche à la dernière place , garantit une erreur de moins de la moitié d'une unité à la dernière place pour une opération. L'utilisation de la troncature, de l'arrondi et de l'arrondi seuls peut entraîner une erreur supérieure à la moitié d'une unité à la dernière place, mais inférieure à une unité à la dernière place, ces modes ne sont donc pas recommandés à moins qu'ils ne le soient utilisé en arithmétique d'intervalle.

6. Résumé

En bref, la raison fondamentale des erreurs dans les opérations en virgule flottante est une combinaison de la troncature dans le matériel et de la troncature d'une réciproque dans le cas de la division. Étant donné que la norme IEEE-754 ne requiert qu'une erreur de moins de la moitié d'une unité à la dernière place pour une seule opération, les erreurs en virgule flottante sur les opérations répétées s'additionneront sauf si elles sont corrigées.

KernelPanik
la source
8
(3) est faux. L'erreur d'arrondi dans une division n'est pas inférieure à une unité à la dernière place, mais au plus à une demi -unité à la dernière place.
gnasher729
6
@ gnasher729 Bonne prise. La plupart des opérations de base ont également une erreur de moins de la moitié d'une unité en dernier lieu en utilisant le mode d'arrondi IEEE par défaut. Modifié l'explication, et a également noté que l'erreur peut être supérieure à 1/2 d'un ulp mais inférieure à 1 ulp si l'utilisateur remplace le mode d'arrondi par défaut (cela est particulièrement vrai dans les systèmes embarqués).
KernelPanik
39
(1) Les nombres à virgule flottante ne comportent pas d'erreur. Chaque valeur en virgule flottante est exactement ce qu'elle est. La plupart (mais pas toutes) des opérations en virgule flottante donnent des résultats inexacts. Par exemple, il n'y a pas de valeur à virgule flottante binaire qui soit exactement égale à 1,0 / 10,0. Certaines opérations (par exemple, 1,0 + 1,0) ne donnent des résultats exacts d'autre part.
Solomon Slow
19
"La principale cause de l'erreur dans la division en virgule flottante, sont les algorithmes de division utilisés pour calculer le quotient" est une chose très trompeuse à dire. Pour une division conforme IEEE-754, la seule cause d'erreur dans la division en virgule flottante est l'incapacité du résultat à être représenté exactement dans le format de résultat; le même résultat est calculé quel que soit l'algorithme utilisé.
Stephen Canon
6
@Matt Désolé pour la réponse tardive. C'est essentiellement dû à des problèmes de ressources / temps et à des compromis. Il existe un moyen de faire une division longue / une division plus `` normale '', elle s'appelle Division SRT avec deux radix. Cependant, cela décale et soustrait à plusieurs reprises le diviseur du dividende et prend de nombreux cycles d'horloge car il ne calcule qu'un bit du quotient par cycle d'horloge. Nous utilisons des tableaux de réciproques afin que nous puissions calculer plus de bits du quotient par cycle et faire des compromis performances / vitesse efficaces.
KernelPanik
464

Lorsque vous convertissez 0,1 ou 1/10 en base 2 (binaire), vous obtenez un motif répétitif après le point décimal, tout comme essayer de représenter 1/3 en base 10. La valeur n'est pas exacte, et donc vous ne pouvez pas faire calcul exact avec elle en utilisant des méthodes normales en virgule flottante.

Joel Coehoorn
la source
133
Grande et courte réponse. Le motif répétitif ressemble à 0,00011001100110011001100110011001100110011001100110011 ...
Konstantin Chernov
4
Cela n'explique pas pourquoi un meilleur algorithme n'est pas utilisé qui ne se convertit pas en binaires en premier lieu.
Dmitri Zaitsev
12
Parce que la performance. L'utilisation du binaire est quelques milliers de fois plus rapide, car elle est native pour la machine.
Joel Coehoorn
7
Il existe des méthodes qui donnent des valeurs décimales exactes. BCD (décimal codé binaire) ou diverses autres formes de nombre décimal. Cependant, ceux-ci sont à la fois plus lents (beaucoup plus lents) et prennent plus de stockage que l'utilisation de virgule flottante binaire. (à titre d'exemple, BCD compressé stocke 2 chiffres décimaux dans un octet. C'est 100 valeurs possibles dans un octet qui peut réellement stocker 256 valeurs possibles, ou 100/256, ce qui gaspille environ 60% des valeurs possibles d'un octet.)
Duncan C
16
@Jacksonkr vous pensez toujours en base-10. Les ordinateurs sont en base 2.
Joel Coehoorn
308

La plupart des réponses ici abordent cette question en termes techniques très secs. Je voudrais aborder cela en termes que les êtres humains normaux peuvent comprendre.

Imaginez que vous essayez de couper des pizzas. Vous avez un coupe-pizza robotisé qui peut couper les tranches de pizza exactement de moitié. Il peut réduire de moitié une pizza entière, ou il peut diviser par deux une tranche existante, mais dans tous les cas, la réduction de moitié est toujours exacte.

Ce coupe-pizza a des mouvements très fins, et si vous commencez avec une pizza entière, puis la divisez en deux et continuez de diviser la plus petite tranche à chaque fois, vous pouvez effectuer la division de 53 fois avant que la tranche ne soit trop petite pour ses capacités de haute précision . À ce stade, vous ne pouvez plus diviser par deux cette tranche très mince, mais vous devez l'inclure ou l'exclure telle quelle.

Maintenant, comment décomposeriez-vous toutes les tranches de manière à ce que cela représente un dixième (0,1) ou un cinquième (0,2) d'une pizza? Pensez-y vraiment, et essayez de le résoudre. Vous pouvez même essayer d'utiliser une vraie pizza, si vous avez un coupe-pizza de précision mythique à portée de main. :-)


La plupart des programmeurs expérimentés connaissent bien sûr la vraie réponse, à savoir qu'il n'y a aucun moyen de reconstituer exactement un dixième ou un cinquième de la pizza en utilisant ces tranches, quelle que soit la finesse de vos tranches. Vous pouvez faire une assez bonne approximation, et si vous additionnez l'approximation de 0,1 à l'approximation de 0,2, vous obtenez une assez bonne approximation de 0,3, mais c'est toujours juste cela, une approximation.

Pour les nombres à double précision (qui est la précision qui vous permet de diviser par deux votre pizza 53 fois), les nombres immédiatement inférieurs et supérieurs à 0,1 sont 0,0999999999999999999167332731531132594682276248931884765625 et 0,1000000000000000055511151231257827021181583404541015625. Ce dernier est un peu plus proche de 0,1 que le premier, donc un analyseur numérique sera, compte tenu d'une entrée de 0,1, favoriser le second.

(La différence entre ces deux nombres est la "plus petite tranche" que nous devons décider d'inclure, ce qui introduit un biais vers le haut, ou d'exclure, ce qui introduit un biais vers le bas. Le terme technique pour cette plus petite tranche est un ulp .)

Dans le cas de 0,2, les chiffres sont tous les mêmes, juste augmentés d'un facteur de 2. Encore une fois, nous privilégions la valeur légèrement supérieure à 0,2.

Notez que dans les deux cas, les approximations de 0,1 et 0,2 ont un léger biais à la hausse. Si nous ajoutons suffisamment de ces biais, ils repousseront le nombre de plus en plus loin de ce que nous voulons, et en fait, dans le cas de 0,1 + 0,2, le biais est suffisamment élevé pour que le nombre résultant ne soit plus le nombre le plus proche. à 0,3.

En particulier, 0,1 + 0,2 + est vraiment 0.1000000000000000055511151231257827021181583404541015625 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125, alors que le nombre le plus proche de 0,3 est en fait 0,299999999999999988897769753748434595763683319091796875.


PS Certains langages de programmation fournissent également des coupe-pizza qui peuvent diviser les tranches en dixièmes exacts . Bien que de tels coupe-pizzas soient rares, si vous en avez un, vous devez l'utiliser quand il est important de pouvoir obtenir exactement un dixième ou un cinquième d'une tranche.

(Publié à l'origine sur Quora.)

Chris Jester-Young
la source
3
Notez qu'il existe certaines langues qui incluent les mathématiques exactes. Un exemple est Scheme, par exemple via GNU Guile. Voir draketo.de/english/exact-math-to-the-rescue - ceux-ci conservent les calculs sous forme de fractions et ne se découpent qu'à la fin.
Arne Babenhauserheide
5
@FloatingRock En fait, très peu de langages de programmation traditionnels ont des nombres rationnels intégrés. Arne est un Schemer, comme moi, donc ce sont des choses qui nous gâtent.
Chris Jester-Young,
5
@ArneBabenhauserheide Je pense qu'il vaut la peine d'ajouter que cela ne fonctionnera qu'avec des nombres rationnels. Donc, si vous faites des calculs avec des nombres irrationnels comme pi, vous devrez le stocker comme un multiple de pi. Bien entendu, tout calcul impliquant pi ne peut pas être représenté comme un nombre décimal exact.
Aidiakapi
13
@connexo D'accord. Comment programmeriez-vous votre rotateur de pizza pour obtenir 36 degrés? Qu'est-ce que 36 degrés? (Astuce: si vous êtes en mesure de définir cela de manière exacte, vous avez également un coupe-pizza dix tranches exactes.) En d'autres termes, vous ne pouvez pas réellement avoir 1/360 (un degré) ou 1 / 10 (36 degrés) avec uniquement une virgule flottante binaire.
Chris Jester-Young
12
@connexo De plus, "chaque idiot" ne peut pas faire tourner une pizza exactement à 36 degrés. Les humains sont trop sujets aux erreurs pour faire quelque chose d'aussi précis.
Chris Jester-Young
212

Erreurs d'arrondi à virgule flottante. 0,1 ne peut pas être représenté aussi précisément en base 2 qu'en base 10 en raison du facteur premier manquant de 5. Tout comme 1/3 prend un nombre infini de chiffres à représenter en décimal, mais est "0,1" en base-3, 0,1 prend un nombre infini de chiffres en base-2 alors qu'il ne le fait pas en base-10. Et les ordinateurs n'ont pas une quantité infinie de mémoire.

Devin Jeanpierre
la source
133
les ordinateurs n'ont pas besoin d'une quantité infinie de mémoire pour obtenir 0,1 + 0,2 = 0,3 à droite
Pacerier
23
@Pacerier Bien sûr, ils pourraient utiliser deux entiers de précision illimitée pour représenter une fraction, ou ils pourraient utiliser la notation de citation. C'est la notion spécifique de "binaire" ou "décimal" qui rend cela impossible - l'idée que vous avez une séquence de chiffres binaires / décimaux et, quelque part là-dedans, un point radix. Pour obtenir des résultats rationnels précis, nous aurions besoin d'un meilleur format.
Devin Jeanpierre
15
@Pacerier: Ni virgule flottante binaire ni décimale ne peut stocker avec précision 1/3 ou 1/13. Les types décimaux à virgule flottante peuvent représenter avec précision les valeurs de la forme M / 10 ^ E, mais sont moins précis que les nombres binaires à virgule flottante de taille similaire lorsqu'il s'agit de représenter la plupart des autres fractions . Dans de nombreuses applications, il est plus utile d'avoir une précision plus élevée avec des fractions arbitraires que d'avoir une précision parfaite avec quelques-unes "spéciales".
supercat
13
@Pacerier Ils le font s'ils stockent les nombres sous forme de flottants binaires, ce qui était le point de la réponse.
Mark Amery
3
@chux: La différence de précision entre les types binaires et décimaux n'est pas énorme, mais la différence de 10: 1 entre la précision dans le meilleur et le pire des cas pour les types décimaux est beaucoup plus grande que la différence de 2: 1 avec les types binaires. Je suis curieux de savoir si quelqu'un a construit du matériel ou des logiciels écrits pour fonctionner efficacement sur l'un ou l'autre des types décimaux, car ni l'un ni l'autre ne semblerait se prêter à une implémentation efficace dans le matériel ou les logiciels.
supercat
121

En plus des autres bonnes réponses, vous pouvez envisager de mettre à l'échelle vos valeurs pour éviter les problèmes d'arithmétique à virgule flottante.

Par exemple:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... au lieu de:

var result = 0.1 + 0.2;     // result === 0.3 returns false

L'expression 0.1 + 0.2 === 0.3revient falseen JavaScript, mais heureusement l'arithmétique entière en virgule flottante est exacte, de sorte que les erreurs de représentation décimale peuvent être évitées en mettant à l'échelle.

À titre d'exemple pratique, pour éviter les problèmes de virgule flottante où la précision est primordiale, il est recommandé 1 de gérer l'argent comme un entier représentant le nombre de cents: 2550cents au lieu de 25.50dollars.


1 Douglas Crockford: JavaScript: The Good Parts : Annexe A - Awful Parts (page 105) .

Daniel Vassallo
la source
3
Le problème est que la conversion elle-même est inexacte. 16,08 * 100 = 1607,9999999999998. Faut-il recourir à la division du nombre et à la conversion séparément (comme dans 16 * 100 + 08 = 1608)?
Jason
38
La solution ici est de faire tous vos calculs en entier puis de diviser par votre proportion (100 dans ce cas) et d'arrondir uniquement lors de la présentation des données. Cela garantira que vos calculs seront toujours précis.
David Granado
16
Juste un petit peu: l'arithmétique entière n'est exacte qu'en virgule flottante jusqu'à un point (jeu de mots voulu). Si le nombre est supérieur à 0x1p53 (pour utiliser la notation à virgule flottante hexadécimale de Java 7, = 9007199254740992), alors l'ulp est 2 à ce point et donc 0x1p53 + 1 est arrondi à 0x1p53 (et 0x1p53 + 3 est arrondi à 0x1p53 + 4, en raison du rond-même). :-D Mais certainement, si votre nombre est inférieur à 9 quadrillions, ça devrait aller. :-P
Chris Jester-Young
2
Jason, vous devriez juste arrondir le résultat (int) (16.08 * 100 + 0.5)
Mikhail Semenov
@CodyBugstein " Alors, comment obtenez-vous .1 + .2 pour afficher .3? " Écrivez une fonction d'impression personnalisée pour placer la décimale où vous le souhaitez.
RonJohn
113

Ma réponse est assez longue, je l'ai donc divisée en trois sections. Puisque la question concerne les mathématiques à virgule flottante, j'ai mis l'accent sur ce que fait réellement la machine. Je l'ai également rendu spécifique à la précision double (64 bits), mais l'argument s'applique également à toute arithmétique à virgule flottante.

Préambule

Un nombre au format à virgule flottante binaire double précision IEEE 754 (binaire64) représente un numéro de la forme

valeur = (-1) ^ s * (1.m 51 m 50 ... m 2 m 1 m 0 ) 2 * 2 e-1023

en 64 bits:

  • Le premier bit est le bit de signe : 1si le nombre est négatif, 0sinon 1 .
  • Les 11 bits suivants sont l' exposant , qui est décalé de 1023. En d'autres termes, après avoir lu les bits d'exposant d'un nombre à double précision, 1023 doit être soustrait pour obtenir la puissance de deux.
  • Les 52 bits restants sont la signification (ou mantisse). Dans la mantisse, un «implicite» 1.est toujours 2 omis puisque le bit le plus significatif de toute valeur binaire est 1.

1 - IEEE 754 permet le concept d'un zéro signé - +0et -0sont traités différemment: 1 / (+0)est l'infini positif; 1 / (-0)est l'infini négatif. Pour les valeurs nulles, les bits de mantisse et d'exposant sont tous nuls. Remarque: les valeurs nulles (+0 et -0) ne sont explicitement pas classées comme dénormales 2 .

2 - Ce n'est pas le cas pour les nombres dénormaux , qui ont un exposant de décalage de zéro (et un implicite 0.). La plage des nombres dénormaux à double précision est d min ≤ | x | ≤ d max , où d min (le plus petit nombre non nul représentable) est 2 -1023 - 51 (≈ 4,94 * 10 -324 ) et d max (le plus grand nombre dénormal, pour lequel la mantisse est entièrement composée de 1s) est 2 -1023 + 1 - 2 - 1023 - 51 (≈ 2,225 * 10 - 308 ).


Transformer un nombre double précision en binaire

De nombreux convertisseurs en ligne existent pour convertir un nombre à virgule flottante double précision en binaire (par exemple sur binaryconvert.com ), mais voici un exemple de code C # pour obtenir la représentation IEEE 754 pour un nombre à double précision (je sépare les trois parties par des deux-points ( :) :

public static string BinaryRepresentation(double value)
{
    long valueInLongType = BitConverter.DoubleToInt64Bits(value);
    string bits = Convert.ToString(valueInLongType, 2);
    string leadingZeros = new string('0', 64 - bits.Length);
    string binaryRepresentation = leadingZeros + bits;

    string sign = binaryRepresentation[0].ToString();
    string exponent = binaryRepresentation.Substring(1, 11);
    string mantissa = binaryRepresentation.Substring(12);

    return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}

Aller droit au but: la question d'origine

(Passer au bas pour la version TL; DR)

Cato Johnston (le poseur de questions) a demandé pourquoi 0,1 + 0,2! = 0,3.

Ecrit en binaire (avec deux points séparant les trois parties), les représentations IEEE 754 des valeurs sont:

0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010

Notez que la mantisse est composée de chiffres récurrents de 0011. Ceci est essentiel à la raison pour laquelle il y a une erreur dans les calculs - 0,1, 0,2 et 0,3 ne peuvent être représentés en binaire avec précision dans un fini nombre de bits binaires , pas plus que 1/9, 1/3 ou 1/7 peuvent être représentées avec précision dans chiffres décimaux .

Notez également que nous pouvons diminuer la puissance de l'exposant de 52 et déplacer le point de la représentation binaire vers la droite de 52 endroits (un peu comme 10 -3 * 1.23 == 10 -5 * 123). Cela nous permet alors de représenter la représentation binaire comme la valeur exacte qu'elle représente sous la forme a * 2 p . où 'a' est un entier.

La conversion des exposants en décimales, la suppression de l'offset et le rajout des valeurs implicites 1(entre crochets), 0,1 et 0,2 sont:

0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125

Pour ajouter deux nombres, l'exposant doit être le même, c'est-à-dire:

0.1 => 2^-3 *  0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 *  1.1001100110011001100110011001100110011001100110011010
sum =  2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397  = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794  = 0.200000000000000011102230246251565404236316680908203125
sum =  2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875

Puisque la somme n'est pas de la forme 2 n * 1. {bbb}, nous augmentons l'exposant de un et décalons le point décimal ( binaire ) pour obtenir:

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)
    = 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875

Il y a maintenant 53 bits dans la mantisse (le 53e est entre crochets dans la ligne ci-dessus). Le mode d'arrondi par défaut pour IEEE 754 est ' Arrondir au plus proche ' - c'est-à-dire que si un nombre x se situe entre deux valeurs a et b , la valeur où le bit le moins significatif est zéro est choisie.

a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
  = 2^-2  * 1.0011001100110011001100110011001100110011001100110011

x = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)

b = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
  = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

Notez que a et b ne diffèrent que dans le dernier bit; ...0011+ 1= ...0100. Dans ce cas, la valeur avec le bit le moins significatif de zéro est b , donc la somme est:

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
    = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

alors que la représentation binaire de 0,3 est:

0.3 => 2^-2  * 1.0011001100110011001100110011001100110011001100110011
    =  2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875

qui ne diffère que de la représentation binaire de la somme de 0,1 et 0,2 par 2 -54 .

Les représentations binaires de 0,1 et 0,2 sont les représentations les plus précises des nombres autorisés par IEEE 754. L'ajout de ces représentations, en raison du mode d'arrondi par défaut, donne une valeur qui ne diffère que par le bit le moins significatif.

TL; DR

Écrire 0.1 + 0.2dans une représentation binaire IEEE 754 (avec deux points séparant les trois parties) et la comparer à 0.3, c'est (j'ai mis les bits distincts entre crochets):

0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3       => 0:01111111101:0011001100110011001100110011001100110011001100110[011]

Reconverties en décimales, ces valeurs sont:

0.1 + 0.2 => 0.300000000000000044408920985006...
0.3       => 0.299999999999999988897769753748...

La différence est exactement 2 -54 , ce qui est ~ 5,5511151231258 × 10 -17 - insignifiant (pour de nombreuses applications) par rapport aux valeurs d'origine.

La comparaison des derniers bits d'un nombre à virgule flottante est intrinsèquement dangereuse, comme le sait quiconque lit le fameux " Ce que tout informaticien devrait savoir sur l'arithmétique à virgule flottante " (qui couvre toutes les parties principales de cette réponse) le sait.

La plupart des calculatrices utilisent des chiffres de garde supplémentaires pour contourner ce problème, ce 0.1 + 0.2qui donne 0.3: les derniers bits sont arrondis.

Wai Ha Lee
la source
14
Ma réponse a été rejetée peu de temps après l'avoir publiée. J'ai depuis apporté de nombreuses modifications (notamment en notant explicitement les bits récurrents lors de l'écriture de 0,1 et 0,2 en binaire, que j'avais omis dans l'original). Au cas où le votant ne verrait pas cela, pourriez-vous s'il vous plaît me donner des commentaires afin que je puisse améliorer ma réponse? Je pense que ma réponse ajoute quelque chose de nouveau puisque le traitement de la somme dans IEEE 754 n'est pas couvert de la même manière dans les autres réponses. Alors que "Ce que tout informaticien devrait savoir ..." couvre le même matériel, ma réponse traite spécifiquement du cas de 0,1 + 0,2.
Wai Ha Lee
57

Les nombres à virgule flottante stockés dans l'ordinateur se composent de deux parties, un entier et un exposant vers lequel la base est prise et multipliée par la partie entière.

Si l'ordinateur fonctionnait en base 10, ce 0.1serait 1 x 10⁻¹, ce 0.2serait 2 x 10⁻¹et ce 0.3serait 3 x 10⁻¹. Les mathématiques entières sont faciles et exactes, donc l'ajout 0.1 + 0.2se traduira évidemment par 0.3.

Les ordinateurs ne fonctionnent généralement pas en base 10, ils fonctionnent en base 2. Vous pouvez toujours obtenir des résultats exacts pour certaines valeurs, par exemple 0.5est 1 x 2⁻¹et 0.25est 1 x 2⁻², et en les ajoutant 3 x 2⁻², ou 0.75. Exactement.

Le problème vient des nombres qui peuvent être représentés exactement en base 10, mais pas en base 2. Ces nombres doivent être arrondis à leur équivalent le plus proche. En supposant le format à virgule flottante IEEE 64 bits très courant, le nombre le plus proche de 0.1est 3602879701896397 x 2⁻⁵⁵et le nombre le plus proche de 0.2est 7205759403792794 x 2⁻⁵⁵; les ajouter ensemble donne 10808639105689191 x 2⁻⁵⁵une valeur décimale exacte de 0.3000000000000000444089209850062616169452667236328125. Les nombres à virgule flottante sont généralement arrondis pour l'affichage.

Mark Ransom
la source
2
@Mark Merci pour cette explication claire mais la question se pose alors de savoir pourquoi 0,1 + 0,4 correspond exactement à 0,5 (au moins en Python 3). Quelle est également la meilleure façon de vérifier l'égalité lors de l'utilisation de flottants dans Python 3?
pchegoor
2
@ user2417881 Les opérations à virgule flottante IEEE ont des règles d'arrondi pour chaque opération, et parfois l'arrondi peut produire une réponse exacte même lorsque les deux nombres sont légèrement décalés. Les détails sont trop longs pour un commentaire et je ne suis pas un expert en tout cas. Comme vous le voyez dans cette réponse, 0,5 est l'une des rares décimales pouvant être représentées en binaire, mais ce n'est qu'une coïncidence. Pour les tests d'égalité, voir stackoverflow.com/questions/5595425/… .
Mark Ransom
1
@ user2417881 votre question m'a intrigué alors je l'ai transformé en une question complète et réponse: stackoverflow.com/q/48374522/5987
Mark Ransom
47

Erreur d'arrondi en virgule flottante. De ce que tout informaticien devrait savoir sur l'arithmétique à virgule flottante :

La compression d'une infinité de nombres réels en un nombre fini de bits nécessite une représentation approximative. Bien qu'il existe une infinité de nombres entiers, dans la plupart des programmes, le résultat des calculs d'entiers peut être stocké sur 32 bits. En revanche, étant donné un nombre fixe de bits, la plupart des calculs avec des nombres réels produisent des quantités qui ne peuvent pas être représentées exactement en utilisant autant de bits. Par conséquent, le résultat d'un calcul en virgule flottante doit souvent être arrondi afin de se réinsérer dans sa représentation finie. Cette erreur d'arrondi est la caractéristique du calcul en virgule flottante.

Brett Daniel
la source
33

Ma solution:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

la précision fait référence au nombre de chiffres que vous souhaitez conserver après le point décimal lors de l'ajout.

Justineo
la source
30

Beaucoup de bonnes réponses ont été publiées, mais j'aimerais en ajouter une de plus.

Tous les nombres ne peuvent pas être représentés via des flottants / doubles Par exemple, le nombre "0,2" sera représenté par "0.200000003" en simple précision dans la norme de point flottant IEEE754.

Le modèle pour stocker des nombres réels sous le capot représente des nombres flottants comme

entrez la description de l'image ici

Même si vous pouvez taper 0.2facilement FLT_RADIXet DBL_RADIXvaut 2; pas 10 pour un ordinateur avec FPU qui utilise la "Norme IEEE pour l'arithmétique à virgule flottante binaire (ISO / IEEE Std 754-1985)".

Il est donc un peu difficile de représenter exactement de tels nombres. Même si vous spécifiez explicitement cette variable sans aucun calcul intermédiaire.

bruziuz
la source
28

Quelques statistiques liées à cette fameuse question de double précision.

Lors de l'ajout de toutes les valeurs ( a + b ) en utilisant un pas de 0,1 (de 0,1 à 100), nous avons ~ 15% de risque d'erreur de précision . Notez que l'erreur peut entraîner des valeurs légèrement plus grandes ou plus petites. Voici quelques exemples:

0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)

En soustrayant toutes les valeurs ( a - ba> b ) en utilisant un pas de 0,1 (de 100 à 0,1), nous avons ~ 34% de chances d'erreur de précision . Voici quelques exemples:

0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)

* 15% et 34% sont en effet énormes, utilisez donc toujours BigDecimal lorsque la précision est d'une grande importance. Avec 2 chiffres décimaux (étape 0.01), la situation s'aggrave un peu plus (18% et 36%).

Kostas Chalkias
la source
28

Non, pas cassé, mais la plupart des fractions décimales doivent être approximées

Sommaire

L'arithmétique en virgule flottante est exacte, malheureusement, elle ne correspond pas bien à notre représentation habituelle des nombres en base 10, il s'avère donc que nous lui donnons souvent une entrée légèrement différente de ce que nous avons écrit.

Même des nombres simples comme 0,01, 0,02, 0,03, 0,04 ... 0,24 ne sont pas représentables exactement comme des fractions binaires. Si vous comptez 0,01, 0,02, 0,03 ..., ce n'est qu'après avoir atteint 0,25 que vous obtiendrez la première fraction représentable en base 2 . Si vous avez essayé cela en utilisant FP, votre 0,01 aurait été légèrement désactivé, donc la seule façon d'en ajouter 25 jusqu'à une bonne 0,25 aurait exigé une longue chaîne de causalité impliquant des bits de garde et des arrondis. C'est difficile à prévoir, alors nous lâchons nos mains et disons "FP is inexact", mais ce n'est pas vraiment vrai.

Nous donnons constamment au matériel FP quelque chose qui semble simple en base 10 mais qui est une fraction répétitive en base 2.

Comment est-ce arrivé?

Lorsque nous écrivons en décimal, chaque fraction (en particulier, chaque décimale terminale) est un nombre rationnel de la forme

           a / (2 n x 5 m )

En binaire, nous n'obtenons que le terme 2 n , c'est-à-dire:

           a / 2 n

Donc , en décimal, on ne peut pas représenter 1 / 3 . Parce que la base 10 comprend 2 comme facteur premier, chaque nombre que nous pouvons écrire comme fraction binaire peut également être écrit comme fraction de base 10. Cependant, presque rien que nous écrivons en tant que fraction de base 10 n'est représentable en binaire. Dans la plage de 0,01, 0,02, 0,03 ... 0,99, seuls trois nombres peuvent être représentés dans notre format FP: 0,25, 0,50 et 0,75, car ils sont 1/4, 1/2 et 3/4, tous les nombres avec un facteur premier utilisant uniquement le terme 2 n .

Dans la base 10 , nous ne pouvons pas représenter 1 / 3 . Mais en binaire, nous ne pouvons pas 1 / 10 ou 1 / 3 .

Ainsi, alors que chaque fraction binaire peut être écrite en décimal, l'inverse n'est pas vrai. Et en fait, la plupart des fractions décimales se répètent en binaire.

Comment y faire face

Les développeurs sont généralement chargés de faire des comparaisons <epsilon , un meilleur conseil pourrait être d'arrondir aux valeurs intégrales (dans la bibliothèque C: round () et roundf (), c'est-à-dire de rester au format FP), puis de comparer. L'arrondi à une longueur de fraction décimale spécifique résout la plupart des problèmes de sortie.

De plus, sur les vrais problèmes de calcul des nombres (les problèmes pour lesquels FP a été inventé sur les premiers ordinateurs terriblement chers), les constantes physiques de l'univers et toutes les autres mesures ne sont connues que d'un nombre relativement petit de chiffres significatifs, donc tout l'espace du problème était "inexact" de toute façon. La «précision» de FP n'est pas un problème dans ce type d'application.

Tout le problème se pose vraiment lorsque les gens essaient d'utiliser la FP pour le comptage des haricots. Cela fonctionne pour cela, mais seulement si vous vous en tenez aux valeurs intégrales, ce qui défait le point de l'utiliser. C'est pourquoi nous avons toutes ces bibliothèques de logiciels de fraction décimale.

J'adore la réponse de Pizza de Chris , car elle décrit le problème réel, pas seulement le geste habituel de "l'inexactitude". Si la PF était simplement "inexacte", nous pourrions corriger cela et l'aurions fait il y a des décennies. La raison pour laquelle nous ne l'avons pas est parce que le format FP est compact et rapide et c'est la meilleure façon de croquer beaucoup de nombres. C'est aussi un héritage de l'ère spatiale et de la course aux armements et des premières tentatives pour résoudre de gros problèmes avec des ordinateurs très lents utilisant de petits systèmes de mémoire. (Parfois, des noyaux magnétiques individuels pour le stockage 1 bit, mais c'est une autre histoire. )

Conclusion

Si vous comptez simplement des beans dans une banque, les solutions logicielles qui utilisent en premier lieu des représentations de chaînes décimales fonctionnent parfaitement. Mais vous ne pouvez pas faire la chromodynamique quantique ou l'aérodynamique de cette façon.

DigitalRoss
la source
L'arrondi à l'entier le plus proche n'est pas un moyen sûr de résoudre le problème de comparaison dans tous les cas. 0.4999998 et 0.500001 arrondissent à différents entiers, il y a donc une "zone de danger" autour de chaque point de coupure d'arrondi. (Je sais que ces chaînes décimales ne sont probablement pas exactement représentables comme des flottants binaires IEEE.)
Peter Cordes
1
De plus, même si la virgule flottante est un format "hérité", il est très bien conçu. Je ne sais rien de ce que quelqu'un changerait s'il le redessinait maintenant. Plus j'en apprends, plus je pense que c'est vraiment bien conçu. Par exemple, l'exposant biaisé signifie que les flotteurs binaires consécutifs ont des représentations entières consécutives, vous pouvez donc implémenter nextafter()avec un incrément ou une décrémentation entière sur la représentation binaire d'un flottant IEEE. En outre, vous pouvez comparer les flottants sous forme d'entiers et obtenir la bonne réponse, sauf lorsqu'ils sont tous les deux négatifs (en raison de l'amplitude des signes par rapport au complément à 2).
Peter Cordes
Je ne suis pas d'accord, les flottants doivent être stockés sous forme décimale et non binaire et tous les problèmes sont résolus.
Ronen Festinger
" X / (2 ^ n + 5 ^ n) " ne devrait-il pas être " x / (2 ^ n * 5 ^ n) "?
Wai Ha Lee
@RonenFestinger - qu'en est-il du 1/3?
Stephen C
19

Avez-vous essayé la solution de ruban adhésif?

Essayez de déterminer quand des erreurs se produisent et corrigez-les avec des instructions if courtes, ce n'est pas joli, mais pour certains problèmes, c'est la seule solution et c'est l'une d'entre elles.

 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                    else { return n * 0.1 + 0.000000000000001 ;}    

J'ai eu le même problème dans un projet de simulation scientifique en c #, et je peux vous dire que si vous ignorez l'effet papillon, ça va se transformer en un gros dragon gras et vous mordre dans le a **

flux de travail
la source
19

Afin d'offrir la meilleure solution, je peux dire que j'ai découvert la méthode suivante:

parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3

Permettez-moi d'expliquer pourquoi c'est la meilleure solution. Comme d'autres l'ont mentionné dans les réponses ci-dessus, c'est une bonne idée d'utiliser la fonction Javascript toFixed () prête à l'emploi pour résoudre le problème. Mais vous rencontrerez probablement des problèmes.

Imaginez que vous allez ajouter deux numéros de flotteur comme 0.2et 0.7voici: 0.2 + 0.7 = 0.8999999999999999.

Votre résultat attendu était que 0.9cela signifie que vous avez besoin d'un résultat avec une précision à 1 chiffre dans ce cas. Vous devriez donc avoir utilisé (0.2 + 0.7).tofixed(1) mais vous ne pouvez pas simplement donner un certain paramètre à toFixed () car cela dépend du nombre donné, par exemple

`0.22 + 0.7 = 0.9199999999999999`

Dans cet exemple, vous avez besoin d'une précision de 2 chiffres toFixed(2), ce qui devrait être le cas, alors quel devrait être le paramètre pour s'adapter à chaque nombre flottant donné?

Vous pourriez dire que ce soit 10 dans chaque situation:

(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"

Zut! Qu'allez-vous faire avec ces zéros indésirables après 9? C'est le moment de le convertir en flotteur pour le faire comme vous le souhaitez:

parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9

Maintenant que vous avez trouvé la solution, il est préférable de l'offrir comme une fonction comme celle-ci:

function floatify(number){
           return parseFloat((number).toFixed(10));
        }

Essayons vous-même:

function floatify(number){
       return parseFloat((number).toFixed(10));
    }
 
function addUp(){
  var number1 = +$("#number1").val();
  var number2 = +$("#number2").val();
  var unexpectedResult = number1 + number2;
  var expectedResult = floatify(number1 + number2);
  $("#unexpectedResult").text(unexpectedResult);
  $("#expectedResult").text(expectedResult);
}
addUp();
input{
  width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>

Vous pouvez l'utiliser de cette façon:

var x = 0.2 + 0.7;
floatify(x);  => Result: 0.9

Comme W3SCHOOLS suggère qu'il existe également une autre solution, vous pouvez multiplier et diviser pour résoudre le problème ci-dessus:

var x = (0.2 * 10 + 0.1 * 10) / 10;       // x will be 0.3

Gardez à l'esprit que (0.2 + 0.1) * 10 / 10cela ne fonctionnera pas du tout, même si cela semble le même! Je préfère la première solution car je peux l'appliquer comme une fonction qui convertit le flotteur d'entrée en flotteur de sortie précis.

Mohammad Musavi
la source
cela m'a fait un vrai mal de tête. Je additionne 12 nombres flottants, puis montre la somme et la moyenne de ces nombres. l'utilisation de toFixed () peut corriger la sommation de 2 nombres, mais lorsque la somme de plusieurs nombres le saut est significatif.
Nuryagdy Mustapayev
@Nuryagdy Mustapayev Je n'ai pas obtenu votre intention, car j'ai testé avant de pouvoir additionner 12 nombres flottants, puis d'utiliser la fonction floatify () sur le résultat, puis de faire ce que vous voulez, je n'ai observé aucun problème à l'utiliser.
Mohammad Musavi
Je dis simplement que dans ma situation où j'ai environ 20 paramètres et 20 formules où le résultat de chaque formule dépend des autres, cette solution n'a pas aidé.
Nuryagdy Mustapayev
16

Ces nombres étranges apparaissent parce que les ordinateurs utilisent le système numérique binaire (base 2) à des fins de calcul, tandis que nous utilisons décimal (base 10).

Il existe une majorité de nombres fractionnaires qui ne peuvent être représentés précisément ni en binaire ni en décimal ou les deux. Résultat - Un nombre arrondi (mais précis) résulte.

Piyush S528
la source
Je ne comprends pas du tout votre deuxième paragraphe.
Nae
1
@Nae Je traduirais le deuxième paragraphe par "La majorité des fractions ne peuvent pas être représentées exactement en décimal ou en binaire. La plupart des résultats seront donc arrondis - bien qu'ils soient toujours précis au nombre de bits / chiffres inhérents à la représentation utilisé."
Steve Summit
15

De nombreux doublons de cette question portent sur les effets de l'arrondi à virgule flottante sur des nombres spécifiques. En pratique, il est plus facile de se faire une idée de son fonctionnement en regardant les résultats exacts des calculs d'intérêt plutôt qu'en lisant simplement à ce sujet. Certains langages offrent des moyens de le faire - comme la conversion d'un floatou doublevers BigDecimalen Java.

Comme il s'agit d'une question indépendante de la langue, elle a besoin d'outils indépendants de la langue, tels qu'un convertisseur décimal en virgule flottante .

En l'appliquant aux nombres de la question, traités comme des doubles:

0,1 convertit en 0,0000000000000000000055511151231257827021181583404541015625,

0,2 convertit en 0,200000000000000011102230246251565404236316680908203125,

0,3 se transforme en 0,29999999999999999988897769753748434595763683319091796875 et

0.30000000000000004 se convertit en 0.3000000000000000444089209850062616169452667236328125.

L'ajout des deux premiers nombres manuellement ou dans une calculatrice décimale telle que la calculatrice de précision complète , montre que la somme exacte des entrées réelles est de 0,3000000000000000166533453693773481063544750213623046875.

S'il était arrondi à l'équivalent de 0,3, l'erreur d'arrondi serait de 0,000000000000000000277555756156289135105907917022705078125. L'arrondi à l'équivalent de 0,30000000000000004 donne également une erreur d'arrondi 0,0000000000000000277555756156289135105907917022705078125. Le disjoncteur égal à égal s'applique.

En revenant au convertisseur à virgule flottante, l'hexadécimal brut pour 0,30000000000000004 est 3fd3333333333334, qui se termine par un chiffre pair et est donc le résultat correct.

Patricia Shanahan
la source
2
Pour la personne dont je viens d'annuler la modification: je considère que les guillemets de code sont appropriés pour citer du code. Cette réponse, étant indépendante de la langue, ne contient aucun code entre guillemets. Les nombres peuvent être utilisés dans des phrases en anglais et cela ne les transforme pas en code.
Patricia Shanahan
C'est probablement la raison pour laquelle quelqu'un a formaté vos nombres sous forme de code - pas pour le formatage, mais pour la lisibilité.
Wai Ha Lee
... aussi, l' arrondi fait même référence à la représentation binaire , pas à la représentation décimale . Voir ceci ou, par exemple, ceci .
Wai Ha Lee
@WaiHaLee Je n'ai appliqué le test pair / impair à aucun nombre décimal, uniquement hexadécimal. Un chiffre hexadécimal est même si, et seulement si, le bit le moins significatif de son expansion binaire est zéro.
Patricia Shanahan
14

Étant donné que personne n'a mentionné cela ...

Certains langages de haut niveau tels que Python et Java sont livrés avec des outils pour surmonter les limitations binaires en virgule flottante. Par exemple:

  • decimalModule Python et BigDecimalclasse Java , qui représentent les nombres en interne avec une notation décimale (par opposition à la notation binaire). Les deux ont une précision limitée, ils sont donc toujours sujets aux erreurs, mais ils résolvent la plupart des problèmes courants avec l'arithmétique binaire à virgule flottante.

    Les décimales sont très bien quand il s'agit d'argent: dix cents plus vingt cents sont toujours exactement trente cents:

    >>> 0.1 + 0.2 == 0.3
    False
    >>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
    True
    

    Le decimalmodule de Python est basé sur la norme IEEE 854-1987 .

  • fractionsModule Python et BigFractionclasse Apache Common . Les deux représentent des nombres rationnels sous forme de (numerator, denominator)paires et peuvent donner des résultats plus précis que l'arithmétique décimale à virgule flottante.

Aucune de ces solutions n'est parfaite (surtout si nous regardons les performances, ou si nous avons besoin d'une très haute précision), mais elles résolvent toujours un grand nombre de problèmes avec l'arithmétique binaire à virgule flottante.

Andrea Corbellini
la source
14

Puis-je simplement ajouter; les gens supposent toujours que c'est un problème informatique, mais si vous comptez avec vos mains (base 10), vous ne pouvez pas obtenir à (1/3+1/3=2/3)=truemoins d'avoir l'infini pour ajouter 0,333 ... à 0,333 ... donc tout comme avec le (1/10+2/10)!==3/10problème de base 2, vous le tronquez à 0,333 + 0,333 = 0,666 et l'arrondissez probablement à 0,667, ce qui serait également techniquement inexact.

Comptez en ternaire, et les tiers ne sont pas un problème cependant - peut-être qu'une course avec 15 doigts sur chaque main demanderait pourquoi votre calcul décimal a été cassé ...


la source
Étant donné que les humains utilisent des nombres décimaux, je ne vois aucune bonne raison pour laquelle les flotteurs ne sont pas représentés par une décimale par défaut, nous avons donc des résultats précis.
Ronen Festinger
Les humains utilisent de nombreuses bases autres que la base 10 (décimales), le binaire étant celui que nous utilisons le plus pour le calcul .. la "bonne raison" est que vous ne pouvez tout simplement pas représenter chaque fraction dans chaque base ..
L'arithmétique binaire @RonenFestinger est facile à implémenter sur les ordinateurs car elle ne nécessite que huit opérations de base avec des chiffres: disons $ a $, $ b $ en $ 0,1 $ tout ce que vous devez savoir est $ \ operatorname {xor} (a, b) $ et $ \ operatorname {cb} (a, b) $, où xor est exclusif ou et cb est le "bit de retenue" qui est $ 0 $ dans tous les cas sauf quand $ a = 1 = b $, auquel cas nous avons un (en fait, la commutativité de toutes les opérations vous fait économiser $ 2 $ cas et tout ce dont vous avez besoin est $ 6 $ règles). L'expansion décimale a besoin de 10 $ \ fois 11 $ (en notation décimale) pour être stockés et 10 $ différents états pour chaque bit et gaspille le stockage sur le carry.
Oskar Limka
@RonenFestinger - Decimal n'est PAS plus précis. C'est ce que dit cette réponse. Pour toute base que vous avez choisie, il y aura des nombres rationnels (fractions) qui donnent des séquences de chiffres répétitifs à l'infini. Pour mémoire, certains des premiers ordinateurs ont utilisé des représentations de base 10 pour les nombres, mais les concepteurs de matériel informatique pionniers ont rapidement conclu que la base 2 était beaucoup plus facile et plus efficace à mettre en œuvre.
Stephen C
9

Le type de calcul à virgule flottante qui peut être implémenté dans un ordinateur numérique utilise nécessairement une approximation des nombres réels et des opérations sur ceux-ci. (La version standard comprend plus de cinquante pages de documentation et dispose d'un comité pour traiter ses errata et les affiner.)

Cette approximation est un mélange d'approximations de différents types, dont chacun peut être ignoré ou soigneusement pris en compte en raison de sa manière spécifique de s'écarter de l'exactitude. Cela implique également un certain nombre de cas exceptionnels explicites au niveau matériel et logiciel que la plupart des gens passent devant tout en faisant semblant de ne pas remarquer.

Si vous avez besoin d'une précision infinie (en utilisant le nombre π, par exemple, au lieu de l'un de ses nombreux remplaçants plus courts), vous devez écrire ou utiliser un programme mathématique symbolique à la place.

Mais si vous êtes d'accord avec l'idée que parfois les mathématiques à virgule flottante ont une valeur floue et que la logique et les erreurs peuvent s'accumuler rapidement, et que vous pouvez écrire vos exigences et vos tests pour permettre cela, alors votre code peut souvent se débrouiller avec ce qui est dedans. votre FPU.

Blair Houghton
la source
9

Juste pour le plaisir, j'ai joué avec la représentation des flotteurs, en suivant les définitions de la norme C99 et j'ai écrit le code ci-dessous.

Le code imprime la représentation binaire des flottants en 3 groupes séparés

SIGN EXPONENT FRACTION

et après cela, il imprime une somme qui, une fois additionnée avec suffisamment de précision, montrera la valeur qui existe réellement dans le matériel.

Ainsi, lorsque vous écrivez float x = 999..., le compilateur transformera ce nombre en une représentation binaire imprimée par la fonction de xxtelle sorte que la somme imprimée par la fonction yysoit égale au nombre donné.

En réalité, cette somme n'est qu'une approximation. Pour le nombre 999 999 999, le compilateur insérera dans la représentation en bits du flotteur le nombre 1 000 000 000

Après le code, je joins une session de console, dans laquelle je calcule la somme des termes pour les deux constantes (moins PI et 999999999) qui existent vraiment dans le matériel, insérées là par le compilateur.

#include <stdio.h>
#include <limits.h>

void
xx(float *x)
{
    unsigned char i = sizeof(*x)*CHAR_BIT-1;
    do {
        switch (i) {
        case 31:
             printf("sign:");
             break;
        case 30:
             printf("exponent:");
             break;
        case 23:
             printf("fraction:");
             break;

        }
        char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
        printf("%d ", b);
    } while (i--);
    printf("\n");
}

void
yy(float a)
{
    int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
    int fraction = ((1<<23)-1)&(*(int*)&a);
    int exponent = (255&((*(int*)&a)>>23))-127;

    printf(sign?"positive" " ( 1+":"negative" " ( 1+");
    unsigned int i = 1<<22;
    unsigned int j = 1;
    do {
        char b=(fraction&i)!=0;
        b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
    } while (j++, i>>=1);

    printf("*2^%d", exponent);
    printf("\n");
}

void
main()
{
    float x=-3.14;
    float y=999999999;
    printf("%lu\n", sizeof(x));
    xx(&x);
    xx(&y);
    yy(x);
    yy(y);
}

Voici une session console dans laquelle je calcule la valeur réelle du flotteur qui existe dans le matériel. J'avais l'habitude bcd'imprimer la somme des termes produits par le programme principal. On peut également insérer cette somme en python replou quelque chose de similaire.

-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872

C'est ça. La valeur de 999999999 est en fait

999999999.999999446351872

Vous pouvez également vérifier bcque -3.14 est également perturbé. N'oubliez pas de définir un scalefacteur bc.

La somme affichée est à l'intérieur du matériel. La valeur que vous obtenez en la calculant dépend de l'échelle que vous définissez. J'ai mis le scalefacteur à 15. Mathématiquement, avec une précision infinie, il semble que ce soit 1 000 000 000.

alinsoar
la source
5

Une autre façon de voir les choses: 64 bits sont utilisés pour représenter les nombres. En conséquence, il n'y a aucun moyen que plus de 2 ** 64 = 18 446 744 073 709 551 616 nombres différents puissent être représentés avec précision.

Cependant, Math dit qu'il existe déjà une infinité de décimales entre 0 et 1. IEE 754 définit un codage pour utiliser ces 64 bits efficacement pour un espace numérique beaucoup plus grand plus NaN et +/- Infinity, donc il y a des écarts entre les nombres représentés avec précision remplis de les chiffres sont approximatifs.

Malheureusement, 0,3 se trouve dans un écart.

Torsten Becker
la source
4

Imaginez travailler en base dix avec, disons, 8 chiffres de précision. Vous vérifiez si

1/3 + 2 / 3 == 1

et apprenez que cela revient false. Pourquoi? Eh bien, en tant que nombres réels, nous avons

1/3 = 0,333 .... et 2/3 = 0,666 ....

Troncature à huit décimales, on obtient

0.33333333 + 0.66666666 = 0.99999999

ce qui est bien sûr différent de 1.00000000exactement 0.00000001.


La situation pour les nombres binaires avec un nombre fixe de bits est exactement analogue. En chiffres réels, nous avons

1/10 = 0,0001100110011001100 ... (base 2)

et

1/5 = 0,0011001100110011001 ... (base 2)

Si nous les tronquions, disons, à sept bits, nous obtiendrions

0.0001100 + 0.0011001 = 0.0100101

tandis que d'autre part,

3/10 = 0,01001100110011 ... (base 2)

qui, tronquée à sept bits, est 0.0100110, et ceux-ci diffèrent exactement 0.0000001.


La situation exacte est légèrement plus subtile car ces nombres sont généralement stockés en notation scientifique. Ainsi, par exemple, au lieu de stocker 1/10 car 0.0001100nous pouvons le stocker comme quelque chose comme 1.10011 * 2^-4, en fonction du nombre de bits que nous avons alloués pour l'exposant et la mantisse. Cela affecte le nombre de chiffres de précision que vous obtenez pour vos calculs.

Le résultat est qu'en raison de ces erreurs d'arrondi, vous ne voulez essentiellement jamais utiliser == sur les nombres à virgule flottante. Au lieu de cela, vous pouvez vérifier si la valeur absolue de leur différence est inférieure à un petit nombre fixe.

Daniel McLaury
la source
4

Depuis Python 3.5, vous pouvez utiliser la math.isclose()fonction pour tester l'égalité approximative:

>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
nauer
la source
3

Étant donné que ce fil s'est ramifié un peu dans une discussion générale sur les implémentations en virgule flottante actuelles, j'ajouterais qu'il existe des projets pour résoudre leurs problèmes.

Jetez un œil à https://posithub.org/ par exemple, qui présente un type de nombre appelé posit (et son prédécesseur unum) qui promet d'offrir une meilleure précision avec moins de bits. Si ma compréhension est correcte, elle résout également le type de problèmes dans la question. Projet assez intéressant, la personne derrière est un mathématicien, le Dr John Gustafson . Le tout est open source, avec de nombreuses implémentations réelles en C / C ++, Python, Julia et C # ( https://hastlayer.com/arithmetics ).

Piedone
la source
3

C'est en fait assez simple. Lorsque vous avez un système de base 10 (comme le nôtre), il ne peut exprimer que des fractions qui utilisent un facteur premier de la base. Les facteurs premiers de 10 sont 2 et 5. Ainsi, 1/2, 1/4, 1/5, 1/8 et 1/10 peuvent tous être exprimés proprement car les dénominateurs utilisent tous des facteurs premiers de 10. En revanche, 1 / 3, 1/6 et 1/7 sont tous des décimales répétitives car leurs dénominateurs utilisent un facteur premier de 3 ou 7. En binaire (ou base 2), le seul facteur premier est 2. Ainsi, vous ne pouvez exprimer des fractions que ne contient que 2 comme facteur premier. En binaire, 1/2, 1/4, 1/8 seraient tous exprimés proprement en décimales. Tandis que 1/5 ou 1/10 répéteraient des décimales. Donc 0,1 et 0,2 (1/10 et 1/5), tout en décimales propres dans un système base 10, sont des décimales répétitives dans le système base 2 sur lequel l'ordinateur fonctionne. Lorsque vous faites des calculs sur ces décimales répétitives,

Depuis https://0.30000000000000004.com/

Vlad Agurets
la source
3

Les nombres décimaux tels que 0.1, 0.2et 0.3ne sont pas représentés exactement dans les types à virgule flottante codés binaires. La somme des approximations pour 0.1et 0.2diffère de l'approximation utilisée pour 0.3, d'où le mensonge de 0.1 + 0.2 == 0.3as peut être vu plus clairement ici:

#include <stdio.h>

int main() {
    printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");
    printf("0.1 is %.23f\n", 0.1);
    printf("0.2 is %.23f\n", 0.2);
    printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);
    printf("0.3 is %.23f\n", 0.3);
    printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));
    return 0;
}

Production:

0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17

Pour que ces calculs soient évalués de manière plus fiable, vous devez utiliser une représentation décimale pour les valeurs à virgule flottante. La norme C ne spécifie pas de tels types par défaut mais comme une extension décrite dans un rapport technique .

Les _Decimal32, _Decimal64et les _Decimal128types peuvent être disponibles sur votre système (par exemple, GCC les prend en charge sur des cibles sélectionnées , mais Clang ne les prend pas en charge sur OS X ).

chqrlie
la source
1

Math.sum (javascript) .... type de remplacement d'opérateur

.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001

Object.defineProperties(Math, {
    sign: {
        value: function (x) {
            return x ? x < 0 ? -1 : 1 : 0;
            }
        },
    precision: {
        value: function (value, precision, type) {
            var v = parseFloat(value), 
                p = Math.max(precision, 0) || 0, 
                t = type || 'round';
            return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
        }
    },
    scientific_to_num: {  // this is from https://gist.github.com/jiggzson
        value: function (num) {
            //if the number is in scientific notation remove it
            if (/e/i.test(num)) {
                var zero = '0',
                        parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
                        e = parts.pop(), //store the exponential part
                        l = Math.abs(e), //get the number of zeros
                        sign = e / l,
                        coeff_array = parts[0].split('.');
                if (sign === -1) {
                    num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
                } else {
                    var dec = coeff_array[1];
                    if (dec)
                        l = l - dec.length;
                    num = coeff_array.join('') + new Array(l + 1).join(zero);
                }
            }
            return num;
         }
     }
    get_precision: {
        value: function (number) {
            var arr = Math.scientific_to_num((number + "")).split(".");
            return arr[1] ? arr[1].length : 0;
        }
    },
    sum: {
        value: function () {
            var prec = 0, sum = 0;
            for (var i = 0; i < arguments.length; i++) {
                prec = this.max(prec, this.get_precision(arguments[i]));
                sum += +arguments[i]; // force float to convert strings to number
            }
            return Math.precision(sum, prec);
        }
    }
});

l'idée est d'utiliser des opérateurs mathématiques à la place pour éviter les erreurs flottantes

Math.sum détecte automatiquement la précision à utiliser

Math.sum accepte n'importe quel nombre d'arguments

bortunac
la source
1
Je ne suis pas sûr que vous ayez répondu à la question " Pourquoi ces inexactitudes se produisent-elles? ".
Wai Ha Lee
d'une certaine manière, vous avez raison, mais je suis venu ici d'un comportement étrange javascript concernant ce problème ... je veux juste partager une sorte de solution
bortunac
Mais vous ne répondez toujours pas à la question.
Wai Ha Lee
k vous avez un problème avec ça ... dites-moi où le déplacer ou si vous insistez je peux juste le supprimer
bortunac
0

Je viens de voir ce problème intéressant autour des virgules flottantes:

Considérez les résultats suivants:

error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1

Nous pouvons clairement voir un point d'arrêt quand 2**53+1- tout fonctionne bien jusqu'à 2**53.

>>> (2**53) - int(float(2**53))
0

Entrez la description de l'image ici

Cela se produit à cause du binaire double précision: format à virgule flottante binaire double précision IEEE 754: binaire64

À partir de la page Wikipedia pour le format à virgule flottante double précision :

La virgule flottante binaire à double précision est un format couramment utilisé sur les PC, en raison de sa gamme plus large sur la virgule flottante à simple précision, malgré ses performances et son coût de bande passante. Comme avec le format à virgule flottante simple précision, il manque de précision sur les nombres entiers par rapport à un format entier de la même taille. Il est communément appelé simplement double. La norme IEEE 754 spécifie un binaire64 comme ayant:

  • Bit de signe: 1 bit
  • Exposant: 11 bits
  • Précision significative: 53 bits (52 explicitement stockés)

Entrez la description de l'image ici

La valeur réelle supposée par une donnée double précision 64 bits donnée avec un exposant biaisé donné et une fraction 52 bits est

Entrez la description de l'image ici

ou

Entrez la description de l'image ici

Merci à @a_guest de me l'avoir signalé.

costargc
la source
-1

Une question différente a été nommée en double de celle-ci:

En C ++, pourquoi le résultat est-il cout << xdifférent de la valeur affichée par un débogueur x?

Le xdans la question est une floatvariable.

Un exemple serait

float x = 9.9F;

Le débogueur montre que 9.89999962la sortie de l' coutopération est 9.9.

La réponse s'avère être coutla précision par défaut de float6, donc elle arrondit à 6 chiffres décimaux.

Voir ici pour référence


la source
1
OMI - publier ceci ici n'était pas la bonne approche. Je sais que c'est frustrant, mais les gens qui ont besoin d'une réponse à la question d'origine (apparemment maintenant supprimée!) Ne la trouveront pas ici. Si vous pensez vraiment que votre travail mérite d'être sauvegardé, je vous suggère: 1) de chercher un autre Q auquel cela répond réellement, 2) de créer une question à réponse automatique.
Stephen C