web-dev-qa-db-fra.com

Les calculs en virgule flottante sont-ils cassés?

Considérons le code suivant:

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

Pourquoi ces inexactitudes se produisent?

2706
Cato Johnston

Binaire virgule flottante Math est comme ça. Dans la plupart des langages de programmation, il est basé sur le norme IEEE 754 . JavaScript utilise une représentation en virgule flottante de 64 bits, identique à double de Java. Le noeud du problème réside dans le fait que les nombres sont représentés dans ce format sous forme de nombre entier multiplié par une puissance de deux; Les nombres rationnels (tels que 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.1 au format standard binary64, la représentation peut être écrite exactement comme

  • 0.1000000000000000055511151231257827021181583404541015625 en décimal, ou
  • 0x1.999999999999ap-4 in 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-4 dans un analogue de la notation hexfloat C99, où ... représente une séquence infinie de 9.

Les constantes 0.2 et 0.3 de votre programme seront également des approximations de leurs valeurs vraies. Il arrive que le plus proche double à 0.2 soit supérieur au nombre rationnel 0.2 mais que le plus proche double à 0.3 soit inférieur au nombre rationnel 0.3. La somme de 0.1 et 0.2 finit par être plus grande que le nombre rationnel 0.3 et ne correspond donc pas à 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 floating-point-gui.de .

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

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

Vous venez de trébucher sur un nombre (3/10) qu'il est facile de représenter avec le système décimal, mais ne correspond pas au système binaire. Cela va également dans les deux sens (dans une certaine mesure): 1/16 est un nombre laid en décimal (0,0625), mais en binaire, il a l'air aussi beau 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 nos vies quotidiennes, vous regarderiez même ce nombre et comprendriez instinctivement que vous pouviez y arriver en divisant par deux quelque chose, en le réduisant encore et encore, encore et encore.

** Bien sûr, ce n'est pas exactement la manière dont les nombres à virgule flottante sont stockés en mémoire (ils utilisent une forme de notation scientifique). Cependant, cela illustre bien le fait que les erreurs de précision binaires en virgule flottante ont tendance à surgir parce que les nombres du "monde réel" avec lesquels nous souhaitons travailler sont si souvent des puissances de dix - seulement parce que nous utilisons un système de nombres décimaux aujourd'hui. C’est aussi pourquoi nous disons 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 sont aussi imparfaits que tout autre système de nombres de base-N :)

Side Side Remarque: Utilisation de flottants dans la programmation

En pratique, ce problème de précision signifie que vous devez utiliser des fonctions d'arrondi pour arrondir vos nombres en virgule flottante à la décimale désirée avant de les afficher.

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

Do not do if (float1 == float2) { ... }

À la place, faites if (Math.Abs(float1 - float2) < myToleranceValue) { ... }.

myToleranceValue doit être choisi pour votre application particulière - et cela dépendra beaucoup de la marge de manœuvre que vous êtes prêt à autoriser et du nombre le plus élevé que vous allez pouvoir comparer (en raison de la perte de précision problèmes). Méfiez-vous des constantes de style "double.Epsilon" dans la langue de votre choix (Number.EPSILON en Javascript). Celles-ci ne doivent pas être utilisées comme valeurs de tolérance.

Plus d'informations sur les tolérances:

(Auto-promotion sans scrupule par un éditeur - désolé pour le détournement)

J'ai rassemblé une explication plus détaillée sur la manière de choisir une tolérance et sur la raison pour laquelle Number.EPSILON et ses semblables sont accessibles à partir de https://dev.to/alldanielscott/how-to-compare-numbers-correctly -in-javascript-1l4i

2048
Brian R. Bondy

Perspective d'un concepteur de matériel

Je pense que je devrais ajouter la perspective du concepteur de matériel à la conception et à la construction de matériel à virgule flottante. Connaître l’origine de l’erreur peut aider à comprendre ce qui se passe dans le logiciel, et au final, j’espère que cela aidera à expliquer les raisons pour lesquelles les erreurs en virgule flottante se produisent et semblent s’accumuler avec le temps.

1. Vue d'ensemble

D'un point de vue technique, la plupart des opérations en virgule flottante comportent un élément d'erreur, car le matériel utilisé pour effectuer les calculs en virgule flottante doit uniquement présenter une erreur inférieure à la moitié d'une unité à la dernière place. Par conséquent, une grande partie du matériel s'arrête à une précision qui n'est nécessaire que pour générer une erreur inférieure à la moitié d'une unité à la dernière place pour une opération unique , ce qui est particulièrement problématique en mode flottant. division de points. Ce qui constitue une seule opération dépend du nombre d'opérandes pris par l'unité. Pour la plupart, il s'agit de deux, mais certaines unités prennent 3 opérandes ou plus. De ce fait, rien ne garantit que des opérations répétées généreront une erreur souhaitable, car ces erreurs s’additionnent avec le temps.

2. normes

La plupart des processeurs sont conformes à 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 de représenter de très petits nombres à virgule flottante au détriment de la précision. Ce qui suit couvrira cependant le mode normalisé de IEEE-754, qui est le mode de fonctionnement typique.

Dans la norme IEEE-754, les concepteurs de matériel sont autorisés à n’importe quelle valeur erreur/epsilon tant qu’il reste moins de la moitié d’une unité à la dernière place et que le résultat doit être inférieur à la moitié d’une unité dans la dernière. place pour une opération. Cela explique pourquoi, s’il ya des opérations répétées, les erreurs s’additionnent. Pour la double précision IEEE-754, il s’agit du 54ème bit, puisque 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 dans 5.3e5). Les sections suivantes détaillent les causes d'erreur matérielle lors de diverses opérations en virgule flottante.

3. Cause de l'arrondi en division

La principale cause de l'erreur dans la division en virgule flottante réside dans 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 en 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 correspond à tout ce qui présente une erreur inférieure à une unité à la dernière place. 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 de la base, ou un nombre de bits de le quotient calculé à chaque itération, plus quelques bits de garde. Pour le standard IEEE-754, double précision (64 bits), ce serait la taille de la base 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 (nombre 4) correspond à 2+2= 4 bits (plus quelques bits facultatifs).

3.1 Erreur d’arrondi de division: approximation de réciproque

Les inversions dans la table de sélection du quotient dépendent de la méthode de division : division lente telle que division SRT, ou division rapide telle que division de Goldschmidt; chaque entrée est modifiée selon l'algorithme de division dans le but de générer l'erreur la plus faible possible. Dans tous les cas, cependant, tous les inverses sont des approximations de l'inverse réelle et introduisent un élément d'erreur. Les méthodes de division lente et de division rapide calculent le quotient de manière itérative: un nombre de bits du quotient est calculé à 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 unité à la dernière place. 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, tandis que les méthodes de division rapide calculent un nombre variable de chiffres par étape et sont généralement plus chères. 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, elles sont donc 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 due aux différents modes de troncature de la réponse finale que permet IEEE-754. Il y a tronqué, arrondi vers zéro, arrondi au plus proche (par défaut), arrondi, et arrondi. Toutes les méthodes introduisent un élément d'erreur de moins d'une unité à la dernière place pour une seule opération. Au fil du temps et des opérations répétées, la troncature ajoute également de manière cumulative à l'erreur résultante. Cette erreur de troncature est particulièrement problématique en exponentiation, qui implique une forme de multiplication répétée.

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

Etant donné que le matériel qui effectue les calculs en virgule flottante doit seulement produire un résultat avec une erreur inférieure à la moitié d'une unité à la dernière place pour une seule opération, l'erreur croîtra si des opérations répétées sont effectuées. C’est la raison pour laquelle les mathématiciens utilisent des méthodes telles que l’arrondi au plus proche même chiffre à la dernière place de IEEE-754, car dans le temps, les erreurs sont plus susceptibles de s’annuler, et arithmétique sur intervalles combiné à des variations du modes d’arrondi IEEE 754 pour prévoir les erreurs d’arrondi et les corriger. En raison de sa faible erreur relative par rapport aux autres modes d’arrondi, le mode d’arrondi par défaut de IEEE-754 est le chiffre arrondi au chiffre pair le plus proche (à la dernière place).

Notez que le mode d'arrondi par défaut, arrondi au plus proche chiffre pair à la dernière place , garantit une erreur inférieure à la moitié d'une unité à la dernière place pour une opération. L'utilisation des troncatures, arrondis et arrondis 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. 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 d'une division. Etant donné que la norme IEEE-754 ne requiert qu'une erreur inférieure à la moitié d'une unité à la dernière place pour une seule opération, les erreurs en virgule flottante générées lors d'opérations répétées s'ajouteront sauf si elles sont corrigées.

570
KernelPanik

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

427
Joel Coehoorn

La plupart des réponses ici abordent cette question en termes très secs et techniques. J'aimerais aborder ce sujet dans des termes que l'homme normal peut comprendre.

Imaginez que vous essayez de couper des pizzas. Vous disposez d'un coupe-pizza robotique capable de couper des tranches de pizza exactement en deux. Elle peut réduire de moitié une pizza entière ou réduire de moitié 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, divisez-le en deux et continuez à diviser par deux la plus petite part à chaque fois, vous pouvez le diviser par deux 53 fois avant que la tranche soit trop petite pour même ses capacités de haute précision. À ce stade, vous ne pouvez plus réduire de moitié cette très fine tranche, mais vous devez l'inclure ou l'exclure tel quel.

Maintenant, comment découperiez-vous toutes les tranches de manière à ajouter jusqu'à un dixième (0,1) ou un cinquième (0,2) d'une pizza? Pensez-y vraiment et essayez de vous en sortir. Vous pouvez même essayer d'utiliser une vraie pizza si vous avez un coupe-pizza de précision mythique à portée de main. :-)


Bien sûr, la plupart des programmeurs expérimentés connaissent la vraie réponse, à savoir qu’il n’ya aucun moyen de reconstituer un exact dixième ou cinquième de la pizza à l’aide de ceux-ci. tranches, peu importe la précision avec laquelle vous les tranchez. Vous pouvez faire une assez bonne approximation, et si vous additionnez l'approximation de 0.1 avec l'approximation de 0.2, vous obtenez une assez bonne approximation de 0.3, mais c'est tout simplement une approximation.

Pour les chiffres en double précision (la précision qui vous permet de diviser par 50 votre pizza est divisée par 53), les chiffres immédiatement inférieurs et supérieurs à 0.1 sont 0.0999999999999999999991673323153113259468221884765625 et 0.10000000000000000555121551215512155. Ce dernier est un peu plus proche de 0,1 que le premier, aussi un analyseur numérique privilégiera-t-il, avec une entrée de 0,1.

(La différence entre ces deux nombres correspond à la "plus petite tranche" que nous devons décider d'inclure, ce qui introduit un biais haussier, ou d'exclure, qui introduit un biais baissier. Le terme technique pour désigner cette plus petite tranche est un lp .)

Dans le cas de 0,2, les chiffres sont tous identiques, mais simplement multipliés par 2. Encore une fois, nous privilégions une valeur légèrement supérieure à 0,2.

Notez que dans les deux cas, les approximations pour 0.1 et 0.2 ont un léger biais à la hausse. Si nous ajoutons assez de ces biais, ils vont pousser 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 réellement.


P.S. Certains langages de programmation fournissent également des coupeurs de pizza pouvant diviser des tranches en dixièmes exacts . Bien que de telles découpeuses de pizza soient rares, si vous en avez accès, vous devez les utiliser quand il est important de pouvoir obtenir exactement un dixième ou un cinquième d'une tranche.

(Initialement posté sur Quora.)

281
Chris Jester-Young

Erreurs d'arrondi en virgule flottante. 0,1 ne peut pas être représenté avec autant de précision en base 2 qu'en base 10 en raison du facteur premier manquant de 5. De même qu'un tiers prend un nombre infini de chiffres à représenter en décimal, mais vaut "0,1" en base 3, 0.1 prend un nombre infini de chiffres en base 2 alors que ce n'est pas le cas en base 10. Et les ordinateurs n'ont pas une quantité infinie de mémoire.

207
Devin Jeanpierre

Outre les autres réponses correctes, vous pouvez envisager de redimensionner vos valeurs pour éviter les problèmes d'arithmétique en 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.3 renvoie false en JavaScript, mais heureusement, l'arithmétique des nombres entiers en virgule flottante est exacte. Vous pouvez ainsi éviter les erreurs de représentation décimales en mettant à l'échelle.

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


1 Douglas Crockford: JavaScript: les bonnes parties : Annexe A - Les terribles parties (page 105) .

116
Daniel Vassallo

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

Préambule

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

valeur = (-1) ^ s * (1.m51m50... m2m1m)2 * 2e-1023

en 64 bits:

  • Le premier bit est le bit de signe : 1 si le nombre est négatif, 0 sinon1.
  • Les 11 bits suivants sont les exposant , ce qui correspond à décalage de 1023. En d'autres termes, après avoir lu les bits d'exposant d'un nombre double précision, il faut soustraire 1023 pour obtenir le pouvoir de deux.
  • Les 52 bits restants sont les significande (ou mantisse). Dans la mantisse, un 'implicite' 1. est toujours2 omis puisque le bit le plus significatif de toute valeur binaire est 1.

1 - IEEE 754 autorise le concept de zéro signé - +0 et -0 sont traités différemment: 1 / (+0) est un infini positif; 1 / (-0) est l'infini négatif. Pour les valeurs nulles, les bits de la mantisse et de l'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 nombres dénormaux , qui ont un exposant de décalage égal à zéro (et un 0. implicite). La plage des nombres de double précision dénormaux est dmin ≤ | x | ≤ dmax, où dmin (le plus petit nombre non nul représentable) est 2-1023 - 51 (4,94 * 10-324) et dmax (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).


Conversion d'un nombre double précision en binaire

Il existe de nombreux convertisseurs en ligne permettant de convertir un nombre à virgule flottante double précision en fichier binaire (par exemple, à binaryconvert.com ), mais voici un exemple de code C # pour obtenir la représentation IEEE 754 d’un nombre à double précision les trois parties avec 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 initiale

(Passez au bas de la page pour la version TL; DR)

Cato Johnston (le demandeur) demandé pourquoi 0,1 + 0,2! = 0,3.

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

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 la clé pour expliquer toute erreur dans les calculs - 0.1, 0.2 et 0.3 ne peuvent pas être représentés en binaire précisément dans un nombre fini de bits binaires supérieurs à 1/9, 1/3 ou 1/7 peuvent être représentés avec précision dans chiffres décimaux .

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

Conversion des exposants en nombres décimaux, suppression du décalage et ajout des valeurs implicites 1 (entre crochets), 0.1 et 0.2 sont les suivants:

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 2n * 1. {bbb} on augmente l'exposant de un et on décale 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 53ème est entre crochets dans la ligne ci-dessus). La valeur par défaut mode d'arrondi pour IEEE 754 est ' Arrondir au plus proche ' - c'est-à-dire 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 par le dernier bit; ...0011 + 1 = ...0100. Dans ce cas, la valeur avec le bit le moins significatif de zéro est b , la somme est donc:

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 cette représentation, en raison du mode d'arrondi par défaut, donne une valeur qui diffère seulement dans le bit le moins significatif.

TL; DR

Écrire 0.1 + 0.2 dans une représentation binaire IEEE 754 (avec des deux points séparant les trois parties) et le 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, qui est ~ 5.5511151231258 × 10-17 - insignifiant (pour de nombreuses applications) par rapport aux valeurs d'origine.

Comparer les derniers bits d'un nombre à virgule flottante est par nature dangereux, comme le lit le fameux " Ce que tout informaticien devrait savoir sur l'arithmétique en virgule flottante " (qui couvre l'essentiel de cette réponse). ) saura.

La plupart des calculatrices utilisent des caractères supplémentaires chiffres de garde pour résoudre ce problème. C'est ainsi que 0.1 + 0.2 donnerait 0.3: les derniers bits sont arrondis.

97
Wai Ha Lee

Les nombres à virgule flottante stockés dans l'ordinateur sont constitués de deux parties, un entier et un exposant auxquels la base est attribuée et multipliée par la partie entière.

Si l'ordinateur travaillait en base 10, 0.1 serait 1 x 10⁻¹, 0.2 serait 2 x 10⁻¹ et 0.3 serait 3 x 10⁻¹. Le calcul des nombres entiers est facile et exact, ajouter 0.1 + 0.2 aura évidemment pour résultat 0.3.

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

Le problème vient avec 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 très courant de virgule flottante IEEE 64 bits, le nombre le plus proche de 0.1 est 3602879701896397 x 2⁻⁵⁵ et le nombre le plus proche de 0.2 est 7205759403792794 x 2⁻⁵⁵; en les additionnant ensemble, on obtient 10808639105689191 x 2⁻⁵⁵ ou une valeur décimale exacte de 0.3000000000000000444089209850062616169452667236328125. Les nombres en virgule flottante sont généralement arrondis pour l'affichage.

54
Mark Ransom

Erreur d'arrondi en virgule flottante. De Ce que chaque informaticien devrait savoir sur l'arithmétique en virgule flottante :

Réduire un nombre infini de nombres réels en un nombre fini de bits nécessite une représentation approximative. Bien qu'il existe un nombre infini d'entiers, dans la plupart des programmes, le résultat des calculs d'entiers peut être stocké sur 32 bits. En revanche, pour un nombre de bits fixe, la plupart des calculs avec des nombres réels produiront des quantités qui ne peuvent pas être représentées exactement avec autant de bits. Par conséquent, le résultat d’un calcul en virgule flottante doit souvent être arrondi pour s’inscrire dans sa représentation finie. Cette erreur d’arrondi est la caractéristique du calcul en virgule flottante.

46
Brett Daniel

Ma solution de contournement:

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

précision fait référence au nombre de chiffres que vous souhaitez conserver après la virgule décimale pendant l'addition.

33
Justineo

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

Tous les nombres ne peuvent pas être représentés via des flottants / doublons . Par exemple, le le nombre "0.2" sera représenté par "0.200000003" en simple précision dans la norme à virgule flottante IEEE754.

Modèle pour les nombres réels de magasin sous le capot représentent les nombres flottants comme

enter image description here

Même si vous pouvez taper 0.2 facilement, FLT_RADIX et DBL_RADIX vaut 2; pas 10 pour un ordinateur avec FPU qui utilise "la norme IEEE pour l'arithmétique binaire en virgule flottante (ISO/IEEE standard 754-1985)".

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

29
bruziuz

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

En ajoutant toutes les valeurs ( a + b ) en utilisant un pas de 0,1 (de 0,1 à 100), on a ~ 15% de chance de précision erreur. 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)

Lors de la soustraction de toutes les valeurs ( a - b a> b ) en utilisant un pas de 0,1 (de 100 à 0,1) nous avons ~ 34% de chance 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 effectivement énormes, utilisez donc toujours BigDecimal lorsque la précision revêt une grande importance. Avec 2 chiffres décimaux (étape 0.01), la situation se détériore un peu plus (18% et 36%).

28
Kostas Chalkias

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

Résumé

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 donnons souvent cette entrée est légèrement différente de ce que nous avons écrit.

Même des nombres simples tels que 0,01, 0,02, 0,03, 0,04 ... 0,24 ne sont pas représentables exactement comme des fractions binaires. Si vous comptez jusqu'à 0,01, 0,02, 0,03 ..., vous obtiendrez la première fraction représentable en base jusqu'à 0,25.2. Si vous aviez essayé la méthode FP, votre 0.01 aurait été légèrement en retrait. La seule façon d’en ajouter 25 jusqu’à un Nice exact de 0.25 aurait donc nécessité une longue chaîne de causalité impliquant des arêtes de garde et des arrondis. C'est difficile à prédire, alors nous levons la main et disons "La PF est inexacte", mais ce n'est pas vraiment vrai.

Nous donnons constamment au matériel FP quelque chose qui semble simple en base 10 mais qui se répète en fraction 2.

Comment cela s'est-il passé?

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

a/(2n x 5m)

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

a/2n

Donc, en décimal, nous ne pouvons pas représenter 1/3. Comme la base 10 inclut 2 comme facteur premier, chaque nombre que nous pouvons écrire sous forme de fraction binaire peut également être écrit sous forme de fraction de base 10. Cependant, pratiquement rien n’écrit comme basedix la fraction 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 ayant un facteur premier n'utilisant que les 2n terme.

À la basedix nous ne pouvons pas représenter 1/3. Mais en binaire, on ne peut pas faire 1/dix  ou  1/3.

Ainsi, bien que chaque fraction binaire puisse ê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.

Traiter avec elle

Il est généralement demandé aux développeurs de faire des comparaisons <epsilon , un meilleur conseil pourrait être de arrondir aux valeurs intégrales (dans la bibliothèque C: round () et roundf () , c’est-à-dire, restez au format FP) puis comparez. Arrondir à une fraction décimale spécifique résout la plupart des problèmes de sortie.

De plus, sur les vrais problèmes de calcul (les problèmes pour lesquels FP a été inventé sur des ordinateurs anciens et terriblement coûteux), les constantes physiques de l'univers et toutes les autres mesures ne sont connues que par un nombre relativement réduit de chiffres significatifs. , de sorte que tout le problème était "inexact" de toute façon. FP "précision" n'est pas un problème dans ce type d'application.

Toute la question se pose réellement lorsque les gens essaient d’utiliser FP pour compter les haricots. Cela fonctionne pour cela, mais seulement si vous vous en tenez à des valeurs intégrales, ce qui va à l’encontre de son utilisation. C'est pourquoi nous avons toutes ces bibliothèques logicielles de fraction décimale.

J'adore la réponse de Pizza par Chris , car elle décrit le problème réel, pas seulement le geste habituel de "l'inexactitude". Si FP était simplement "inexact", nous pourrions corriger cela et l'aurions fait il y a plusieurs décennies. La raison en est que le format FP est compact et rapide et qu’il s’agit du meilleur moyen de calculer beaucoup de chiffres. C'est aussi un héritage de l'ère spatiale et de la course aux armements et des premières tentatives de résolution 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 sur 1 bit, mais c'est ne autre histoire. )

Conclusion

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

26
DigitalRoss

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

Essayez de déterminer quand des erreurs se produisent et de les corriger avec des instructions si courtes, ce n’est pas beau, mais pour certains problèmes, c’est la seule solution et c’est l’une d’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, il se tournera vers un gros dragon et vous mordra dans le **

19
workoverflow

Ces nombres étranges apparaissent car les ordinateurs utilisent le système de nombres binaires (base 2) à des fins de calcul, alors que nous utilisons décimal (base 10).

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

16
Piyush S528

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

Laissez-moi vous expliquer pourquoi c'est la meilleure solution. Comme d’autres réponses citées plus haut, c’est une bonne idée d’utiliser la fonction Javascript prête à utiliser toFixed () pour résoudre le problème. Mais très probablement, vous rencontrerez des problèmes.

Imaginez que vous allez additionner deux nombres flottants tels que 0.2 et 0.7, le voici: 0.2 + 0.7 = 0.8999999999999999.

Votre résultat attendu était 0.9 cela signifie que vous avez besoin d'un résultat avec une précision d'un chiffre dans ce cas. Donc vous auriez dû utiliser (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, ce qui devrait être toFixed(2), alors quel devrait être le paramètre pour correspondre à 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 float 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 fonction:

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 une autre solution, vous pouvez également 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 / 10 ne fonctionnera pas du tout bien qu'il semble le même! Je préfère la première solution car je peux l'appliquer comme une fonction qui convertit le float d'entrée en float de sortie précis.

15
Mohammad lm71

Plusieurs des nombreux doublons de cette question concernent les effets de l'arrondi en virgule flottante sur des nombres spécifiques. En pratique, il est plus facile de comprendre comment cela fonctionne en examinant les résultats exacts des calculs qui présentent un intérêt plutôt qu'en se contentant de lire à ce sujet. Certains langages fournissent des moyens de le faire, tels que la conversion de float ou double en BigDecimal en Java.

S'agissant d'une question indépendante de la langue, des outils tels que convertisseur de nombres décimaux en virgule flottante sont nécessaires.

En l'appliquant aux numéros de la question, traités en double:

0,1 est converti en 0.1000000000000000055511151231257827021181583404541015625,

0,2 est converti en 0.200000000000000011102230246251565404236316680908203125,

0,3 est converti en 0,29999999999999999888897979753748434595763683319091796875 et

0.30000000000000004 se convertit en 0.3000000000000000444089209850062616169452667236328125.

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

Si elle était arrondie à l'équivalent de 0,3, l'erreur d'arrondi serait alors 0.0000000000000000277555756156289135105907917022705078125. Arrondir à l'équivalent de 0.30000000000000004 donne également une erreur d'arrondi 0.0000000000000000002775556156289135105907917022705078125. Le briseur d'égalité arrondi à égal s'applique.

Pour revenir au convertisseur à virgule flottante, l'hexadécimal brut de 0.30000000000000004 est 3fd3333333333334, qui se termine par un chiffre pair et constitue donc le résultat correct.

14
Patricia Shanahan

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

Certains langages de haut niveau tels que Python et Java sont livrés avec des outils permettant de surmonter les limites des nombres à virgule flottante binaires. Par exemple:

  • Python decimal module et Java BigDecimal classe , qui représentent des nombres en interne avec une notation décimale (par opposition à une notation binaire). Tous deux ont une précision limitée, ils sont donc toujours sujets aux erreurs. Cependant, ils résolvent les problèmes les plus courants d'arithmétique binaire en virgule flottante.

    Les décimales sont très agréables lorsqu'il s'agit d'argent: dix centimes plus vingt centimes sont toujours exactement trente centimes:

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

    Le module decimal de Python est basé sur le standard IEEE 854-1987 .

  • Python fractions module et Apache Common BigFraction classe . Les deux représentent des nombres rationnels sous forme de paires _(numerator, denominator)_ 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 examinons les performances ou si nous demandons une très grande précision), mais elles résolvent néanmoins un grand nombre de problèmes d'arithmétique binaire en virgule flottante.

14
Andrea Corbellini

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)=true à moins que vous ne disposiez d'une infinité pour ajouter 0,333 ... à 0,333 ... avec le problème (1/10+2/10)!==3/10 en base 2, vous le tronquez à 0.333 + 0.333 = 0.666 et arrondissez-le 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 de chaque main demanderait pourquoi votre calcul décimal a été cassé ...

14
user1641172

Le type de calcul en virgule flottante qui peut être mis en œuvre dans un ordinateur numérique utilise nécessairement une approximation des nombres réels et des opérations correspondantes. (La version standard contient plus de cinquante pages de documentation et dispose d'un comité pour traiter ses errata et ses améliorations.)

Cette approximation est un mélange d'approximations de différentes sortes, chacune pouvant être ignorée ou soigneusement comptabilisée en raison de son type spécifique d'écart par rapport à l'exactitude. Cela implique également un certain nombre de cas exceptionnels et explicites, tant au niveau du matériel que du logiciel, que la plupart des gens ignorent tout en faisant semblant de ne rien remarquer.

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

Mais si vous êtes d'accord avec l'idée que, parfois, les calculs en virgule flottante ont une valeur floue, que la logique et les erreurs peuvent s'accumuler rapidement et que vous pouvez écrire vos exigences et vos tests pour vous permettre de le faire, votre code peut souvent s'en passer avec ce qu'il contient. votre FPU.

10
Blair Houghton

Juste pour le plaisir, j'ai joué avec la représentation de float, 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 dans 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, indiquera la valeur réelle du matériel.

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

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

Après le code, j'attache une session de console dans laquelle je calcule la somme des termes des deux constantes (moins PI et 999999999) qui existent réellement dans le matériel, insérées 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 de console dans laquelle je calcule la valeur réelle du float existant dans le matériel. J'ai utilisé bc pour imprimer la somme des termes générés par le programme principal. On peut insérer cette somme dans python repl ou 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 avec bc que -3.14 est également perturbé. N'oubliez pas de définir un facteur scale dans bc.

La somme affichée est ce que contient le matériel. La valeur obtenue en la calculant dépend de l'échelle que vous avez définie. J'ai réglé le facteur scale sur 15. Mathématiquement, avec une précision infinie, il semble que ce soit 1 000 000 000.

9
alinsoar

Une autre façon de voir ceci: 64 bits sont utilisés pour représenter des nombres. Par conséquent, il n'y a pas moyen de plus que 2 ** 64 = 18 446 744 073 709 551 616 numéros différents peuvent être représentés avec précision.

Cependant, Math dit qu'il existe déjà une infinité de nombres décimaux compris entre 0 et 1. IEE 754 définit un codage permettant d'utiliser ces 64 bits efficacement pour un espace de nombres beaucoup plus grand plus NaN et +/- Infinity, de sorte qu'il existe des espaces entre les nombres représentés avec précision et remplis avec chiffres seulement approximés.

Malheureusement, 0,3 se situe dans un vide.

5
Torsten Becker

Étant donné que ce fil de discussion s'est un peu ramifié 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 oeil à 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, cela règle également le type de problèmes de la question. Projet assez intéressant, la personne derrière ce projet 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 ).

4
Piedone

Imaginez que vous travaillez en base dix avec, par exemple, 8 chiffres de précision. Vous vérifiez si

1/3 + 2 / 3 == 1

et apprendre que cela retourne false. Pourquoi? Eh bien, en chiffres 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, bien sûr, diffère de 1.00000000 par exactement 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 à, par exemple, sept bits, nous aurions

0.0001100 + 0.0011001 = 0.0100101

tandis que de l'autre côté,

/10 = 0.01001100110011 ... (base 2)

qui, tronqué à sept bits, est 0.0100110, et ceux-ci diffèrent exactement par 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 sous la forme 0.0001100, nous pouvons le stocker sous une forme similaire à 1.10011 * 2^-4, en fonction du nombre de bits 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’à cause de ces erreurs d’arrondi, vous ne voulez en principe 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.

3
Daniel McLaury

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

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

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 nombres décimaux répétés 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 que des fractions 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) alors que les décimales sont nettes dans un système de base 10, répètent des décimales dans le système de base 2 dans lequel l'ordinateur fonctionne. qui sont reportés lorsque vous convertissez le numéro de base 2 (binaire) de l'ordinateur en un numéro de base 10 plus lisible par l'homme.

De https://0.30000000000000004.com/

2
Vlad Agurets

Math.sum (javascript) .... type de remplacement de l'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;
        }
    },
    diff:{
        value: function(A,B){
            var prec = this.max(this.get_precision(A),this.get_precision(B));
            return +this.precision(A-B,prec);
        }
    },
    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 Math à la place des opérateurs pour éviter les erreurs de type float

Math.diff(0.2, 0.11) == 0.09 // true
0.2 - 0.11 == 0.09 // false

notez également que Math.diff et Math.sum détectent automatiquement la précision à utiliser

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

2
bortunac

Les fractions décimales telles que 0.1, 0.2 et 0.3 ne sont pas représentées exactement dans des types à virgule flottante codés en binaire. La somme des approximations pour 0.1 et 0.2 diffère de l'approximation utilisée pour 0.3, d'où le mensonge de 0.1 + 0.2 == 0.3 comme on peut le voir 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;
}

Sortie:

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 ces types par défaut, mais en tant qu’extension décrite dans un Rapport technique . Les types _Decimal32, _Decimal64 et _Decimal128 peuvent être disponibles sur votre système (par exemple, gcc les prend en charge sur cibles sélectionnées , mais clang ne les prend pas en charge sous OS/X).

0
chqrlie

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

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

La x dans la question est une variable float.

Un exemple serait

float x = 9.9F;

Le débogueur affiche 9.89999962, la sortie de l'opération cout est 9.9.

La réponse semble être que la précision par défaut de cout pour float est de 6, de sorte qu'elle arrondit à 6 chiffres décimaux.

Voir ici pour référence

0
Arkadiy

C’était en fait une réponse pour cette question - qui était fermée en tant que duplicata de cette question, while J'étais en train de préparer cette réponse, je ne peux donc pas la poster ici ... donc je posterai ici à la place!


Résumé de la question:

Sur la feuille de calcul, 10^-8/1000 et 10^-11 sont évalués comme égaux , alors qu'ils ne le sont pas dans VBA.

Sur la feuille de calcul, les nombres sont définis par défaut sur la notation scientifique.

Si vous changez les cellules en un format numérique (Ctrl+1) de Number avec 15 points décimaux, vous obtenez:

=10^-11 returns 0.000000000010000
=10^(-8/1000) returns 0.981747943019984

Ainsi, ils ne sont certainement pas les mêmes ... l’un est à peu près égal à zéro et l’autre à peu près à 1.

Excel n'était pas conçu pour traiter extrêmement petits nombres - du moins pas avec l'installation d'origine. Il existe des compléments pour aider à améliorer la précision des nombres.


Excel a été conçu conformément à la norme IEEE pour l’arithmétique binaire en virgule flottante ( IEEE 754 ). La norme définit comment nombres à virgule flottante sont stockés et calculés. La norme IEEE 754 est largement utilisée car elle permet de stocker des nombres à virgule flottante dans un espace raisonnable et de permettre des calculs relativement rapides.

L'avantage de la représentation flottante par rapport à la virgule fixe est qu'elle peut prendre en charge une gamme de valeurs plus étendue. Par exemple, une représentation en virgule fixe comportant 5 chiffres décimaux avec le point décimal placé après le troisième chiffre peut représenter les nombres 123.34, 12.23, 2.45, etc., tandis que la virgule flottante une représentation avec une précision de 5 chiffres peut représenter 1,2345, 12345, 0,00012345, etc. De même, la représentation en virgule flottante permet également des calculs sur une large plage de grandeurs tout en maintenant la précision. Par exemple,

img


Autres références:

0
ashleedawg