2010-07-28 12 views
19

possible en double:
problem in comparing double values in C#Pourquoi cette boucle ne se termine jamais?

Je l'ai lu ailleurs, mais vraiment oublier la réponse donc je demande ici à nouveau. Cette boucle semble jamais fin quel que soit code dans une langue (je test en C#, C++, Java ...):

double d = 2.0; 
while(d != 0.0){ 
    d = d - 0.2; 
} 
+2

N'utilisez jamais '==' avec des valeurs flottantes. Peut-être utiliser quelque chose comme «f> epsilon». – pascal

+20

[Cela faisait quelques jours, je suppose que nous étions en retard.] (Http://docs.sun.com/source/806-3568/ncg_goldberg.html) – GManNickG

+0

En Java, je pense qu'il y a le modificateur "Strictfp" pour de telles situations –

Répondre

32

calculs en virgule flottante ne sont pas tout à fait précis. Vous obtiendrez une erreur de représentation parce que 0.2 n'a pas de représentation exacte en tant que nombre à virgule flottante binaire, de sorte que la valeur ne devienne pas exactement égale à zéro. Essayez d'ajouter une déclaration de débogage pour voir le problème:

double d = 2.0; 
while (d != 0.0) 
{ 
    Console.WriteLine(d); 
    d = d - 0.2; 
} 
 
2 
1,8 
1,6 
1,4 
1,2 
1 
0,8 
0,6 
0,4 
0,2 
2,77555756156289E-16 // Not exactly zero!! 
-0,2 
-0,4 

Une façon de le résoudre est d'utiliser le type decimal.

+1

Et pourquoi il y a des erreurs rondes? Tapez 'double' stocke les chiffres binaires. Puisque 0,2 binaire écrit est une fraction périodique, nous devons couper quelque chose pour écrire le nombre sur N bits. – adf88

27

(Pour une chose que vous n'êtes pas en utilisant la même variable tout au long, mais je vais supposer que est une faute de frappe :)

0.2 est pas vraiment 0,2. C'est la valeur double la plus proche de 0.2. Lorsque vous avez soustrait 10 fois de la valeur 2.0, vous ne finirez pas avec exactement 0.0.

En C#, vous pouvez changer d'utiliser le lieu de type decimal, qui fonctionnera:

// Works 
decimal d = 2.0m; 
while (d != 0.0m) { 
    d = d - 0.2m; 
} 

Cela fonctionne parce que le type décimal ne représentent des valeurs décimales comme 0,2 précisément (dans les limites, c'est un 128- type de bit). Chaque valeur impliquée est précisément représentable, donc cela fonctionne. Qu'est-ce que ne travail serait ceci:

decimal d = 2.0m; 
while (d != 0.0m) { 
    d = d - 1m/3m; 
} 

Ici, « un troisième » est pas exactement représentable si nous nous retrouvons avec le même problème qu'auparavant.

En général, c'est une mauvaise idée d'effectuer des comparaisons d'égalité exactes entre des nombres à virgule flottante - généralement, vous les comparez avec une certaine tolérance.

J'ai des articles sur floating binary point et floating decimal point d'un contexte C# /. NET, qui expliquent les choses plus en détail.

1

f est uninitialised;)

Si vous voulez dire:

double f = 2.0; 

Cela peut être un effet de arthimetic non précises sur les variables doubles.

3

Vous feriez mieux d'utiliser

while(f > 0.0) 

* modifier: Voir le commentaire ci-dessous pascals. Mais si vous devez exécuter une boucle un nombre entier et déterministe, utilisez plutôt un type de données intégral.

+0

Je sais que je devrais l'utiliser, mais pourquoi f! = 0.0 ne fonctionne pas? –

+1

Vous pourriez courir une fois de trop, si le dernier 'f' est 2.0e-16 ... – pascal

1

c'est à cause de la précision du point flottant. utiliser while (d> 0.0), ou si vous le devez,

while (Math.abs(d-0.0) > some_small_value){ 

} 
2

Le problème est l'arithmétique à virgule flottante. S'il n'y a pas de représentation binaire exacte pour un nombre, vous ne pouvez y stocker que le nombre le plus proche (tout comme vous ne pouvez pas stocker le nombre 1/3 en décimal - vous ne pouvez stocker que 0.33333333 pour une longueur de '3'). Cela signifie que l'arithmétique sur les nombres à virgule flottante n'est souvent pas totalement précise. Essayez quelque chose comme (Java):

public class Looping { 

    public static void main(String[] args) { 

     double d = 2.0; 
     while(d != 0.0 && d >= 0.0) { 
      System.out.println(d); 
      d = d - 0.2; 
     } 

    } 

} 

Votre sortie doit être quelque chose comme:

2.0 
1.8 
1.6 
1.4000000000000001 
1.2000000000000002 
1.0000000000000002 
0.8000000000000003 
0.6000000000000003 
0.4000000000000003 
0.2000000000000003 
2.7755575615628914E-16 

Et maintenant, vous devriez être en mesure de voir pourquoi la condition d == 0 ne se produit jamais. . (Le dernier numéro il y a un nombre qui est très proche de 0, mais pas tout à fait

Pour un autre exemple de virgule flottante bizarreries, essayez ceci:

public class Squaring{ 

    public static void main(String[] args) { 

     double d = 0.1; 
     System.out.println(d*d); 

    } 

} 

Parce qu'il n'y a pas de représentation binaire exactement 0.1, élevant au carré ne produit pas le résultat que vous attendez (0.01), mais en fait quelque chose comme 0.010000000000000002!

+1

" Jamais totalement exact "l'exagère, IMO. Tout simplement parce que * chaque * valeur décimale n'est pas exactement représentable en virgule flottante binaire ne fait pas l'addition des 0,25 et 0,25 exactement représentés pour obtenir exactement 0,5 moins exact par exemple. –

+0

Édité, merci Jon - c'était un peu exagéré. – Stephen

10

Je me souviens d'acheter un Sinclair ZX-81, travailler mon chemin à travers l'excellent manuel de programmation de base, et presque de retour à la boutique quand je suis tombé sur ma première erreur d'arrondi virgule flottante.

Je n'aurais jamais imaginé que les gens auraient encore ces problèmes 27,99998 ans plus tard.

+2

Le manuel ZX-Spectrum est venu avec une explication détaillée de ce problème. Je me souviens d'avoir beaucoup réfléchi à ce sujet (vers l'âge de 10 ou 11 ans) et de dire «oh, c'est logique». C'était un très bon manuel ... –

+7

+1 pour 27.99998 ans :-) –

0

Il ne s'arrête pas parce que 0,2 i pas précisément représenté en complément à deux de sorte que votre boucle exécute jamais 0.0==0.0 essai

+0

Le complément à deux n'a aucun lien avec les erreurs d'arrondi des nombres à virgule flottante. – Egon

0

Comme d'autres l'ont dit, cela est juste un problème fondamental que vous obtenez lorsque vous faites à virgule flottante arithmétique à toute base. Il se trouve que base-2 est le plus commun dans les ordinateurs (car il admet une implémentation matérielle efficace).

La meilleure solution, si possible, est de passer à l'utilisation d'une représentation quotient du nombre pour votre bouclage, ce qui en déduit la valeur à virgule flottante. OK, ça a l'air exagéré! Pour votre cas, je l'écris comme:

int dTimes10 = 20; 
double d; 
while(dTimes10 != 0) { 
    dTimes10 -= 2; 
    d = dTimes10/10.0; 
} 

Ici, nous travaillons vraiment avec des fractions [20/10, 18/10, 16/10, ..., 2/10, 0/10] où l'itération est faite avec des entiers (c'est-à-dire faciles à obtenir correctement) dans le numérateur avec un dénominateur fixe, avant de convertir en virgule flottante. Si vous pouvez réécrire vos réelles itérations pour qu'elles fonctionnent comme ceci, vous aurez beaucoup de succès (et elles ne sont vraiment pas beaucoup plus chères que ce que vous faisiez auparavant, ce qui est un bon compromis pour obtenir l'exactitude).

Si vous ne pouvez pas faire cela, vous devez utiliser equal-within-epsilon comme comparaison. Approximativement, cela remplace d != target avec abs(d - target) < ε, où la sélection de ε (epsilon) peut parfois être maladroite. Fondamentalement, la bonne valeur de ε dépend d'un tas de facteurs, mais c'est probablement mieux choisi comme 0.001 pour l'itération d'exemple étant donné l'échelle de la valeur d'étape (c'est-à-dire, c'est un demi-pour cent de l'amplitude de l'étape, donc tout ce qui va être erreur plutôt qu'informatif).