2010-02-08 7 views
15

Le code suivant en C# (.Net 3.5 SP1) est une boucle infinie sur ma machine:C# flotteur boucle infinie

for (float i = 0; i < float.MaxValue; i++) ; 

Il a atteint le nombre 16.777.216,0 et 16.777.216,0 + 1 est évalué à 16.777.216,0. Pourtant, à ce stade: i + 1! = I.

Ceci est un peu de folie. Je me rends compte qu'il y a une certaine imprécision dans la façon dont les nombres à virgule flottante sont stockés. Et j'ai lu que des nombres entiers supérieurs à 2^24 ne peuvent pas être correctement stockés comme un flotteur.

Toujours le code ci-dessus, devrait être valide en C# même si le nombre ne peut pas être correctement représenté.

Pourquoi cela ne fonctionne-t-il pas?

Vous pouvez obtenir la même chose pour le double mais cela prend beaucoup de temps. 9007199254740992.0 est la limite pour le double.

+7

Pourquoi utilisez-vous un type à virgule flottante pour un index en premier lieu? – John

+0

Je suis d'accord que ce n'est pas bon code, mais ne devrait-il pas être le bon code? Techniquement, tout nombre plus un devrait être supérieur à lui-même, sauf s'il y a un débordement. – jonathanpeppers

+1

Pas nécessairement. Vous noterez que '16777216.0' est le flotteur de simple précision le plus proche de '16777217.0' –

Répondre

21

droit, de sorte que le problème est que pour ajouter un au flotteur, il devrait devenir

16777217.0 

Il se trouve que ce soit à une limite pour la radix et ne peut pas être représenté exactement comme un flotteur. (La prochaine valeur la plus élevée disponible est 16777218.0)

Ainsi, il arrondit le plus proche flotteur représentable

16777216.0 

Permettez-moi de cette façon:

Puisque vous avez un flottant quantité de précision , vous devez augmenter d'un nombre supérieur et supérieur.

EDIT:

Ok, cela est un peu difficile à expliquer, mais essayez ceci:

float f = float.MaxValue; 
f -= 1.0f; 
Debug.Assert(f == float.MaxValue); 

Cela fonctionnera très bien, parce qu'à cette valeur, afin de représenter un différence de 1.0f, vous auriez besoin de plus de 128 bits de précision. Un flotteur a seulement 32 bits.

EDIT2

Par mes calculs, au moins 128 chiffres binaires non signés seraient nécessaires.

log(3.40282347E+38) * log(10)/log(2) = 128 

En guise de solution à votre problème, vous pouvez parcourir deux numéros de 128 bits. Cependant, cela prendra au moins une décennie à compléter.

+0

C# devrait lui permettre d'être incrémenté, mais non? Tout flotteur + 1 devrait être plus grand que le flotteur lui-même, non? i + 1> je dans tous les cas? Je dirais autrement s'il y avait un débordement, mais le nombre est loin d'être float.MaxValue. – jonathanpeppers

+6

@ Jonathan.Peppers: Je ne suis pas sûr d'où vous avez cette idée fausse sur les systèmes de nombres à virgule flottante. Je vous suggère de lire à leur sujet sur wikipedia, puis réévaluer votre code. http://en.wikipedia.org/wiki/Floating_point – Welbog

+3

Ok, penses-y comme ça: Les flotteurs ont un nombre de chiffres fixe. Près de 'float.MaxValue', la valeur n'est pas précise. En fait, la plupart des chiffres insignifiants sont tronqués. Essayez de soustraire un de float.MaxValue. –

-4

L'itération à l'approche de float.MaxValue a juste en dessous de cette valeur. L'itération suivante ajoute à i, mais ne peut pas contenir un nombre supérieur à float.MaxValue. Ainsi, il détient une valeur beaucoup plus petite et recommence la boucle.

+0

Le code ne déborde pas ... – TJMonk15

6

Pour comprendre ce qui se passe mal, vous allez devoir lire la norme IEEE sur floating point

Examinons la structure d'un numéro floating point pour une seconde:

Un nombre à virgule flottante est divisé en deux parties (ok 3, mais ignorer le bit de signe pendant une seconde).

Vous avez un exposant et une mantisse. Comme si:

smmmmmmmmeeeeeee 

Note: ce n'est pas acurate au nombre de bits, mais il vous donne une idée générale de ce qui se passe.

Pour savoir quel numéro vous nous faites le calcul suivant:

mmmmmm * 2^(eeeeee) * (-1)^s 

Alors qu'est-ce float.MaxValue va être? Eh bien, vous allez avoir la plus grande mantisse possible et le plus grand exposant possible. Feignons cela ressemble quelque chose comme:

01111111111111111 

en réalité nous définissons NAN et + -INF et deux autres conventions, mais les ignorer pour une seconde, car ils ne sont pas pertinents à votre question.

Alors, que se passe-t-il lorsque vous avez 9.9999*2^99 + 1? Eh bien, vous n'avez pas assez de chiffres significatifs à ajouter. En conséquence, il est arrondi au même nombre. Dans le cas de précision à virgule flottante point où +1 commence à s'arrondi se trouve être 16777216.0

8

Imaginez par exemple qu'un nombre à virgule flottante est représenté par jusqu'à 2 chiffres décimaux significatifs, plus un exposant: dans ce cas, vous pourriez compter de 0 à 99 exactement. Le prochain serait 100, mais parce que vous ne pouvez avoir que 2 chiffres significatifs qui seraient stockés comme "1,0 fois 10 à la puissance de 2". En ajouter un à cela serait ... quoi? Au mieux, ce serait 101 comme résultat intermédiaire, qui serait stocké (par une erreur d'arrondi qui supprime le troisième chiffre insignifiant) comme "1,0 fois 10 à la puissance de 2" à nouveau.

2

Cela n'a rien à voir avec un débordement ou une valeur proche de la valeur maximale. La valeur float pour 16777216.0 a une représentation binaire de 16777216. Vous l'incrémentez ensuite de 1, donc ce devrait être 16777217.0, sauf que la représentation binaire de 16777217.0 est 16777216 !!! Il n'est donc pas incrémenté ou du moins l'incrément ne fait pas ce que vous attendiez.

Voici une classe écrite par Jon Skeet qui illustre ceci:

DoubleConverter.cs

Essayez ce code avec elle:

double d1 = 16777217.0; 
Console.WriteLine(DoubleConverter.ToExactString(d1)); 

float f1 = 16777216.0f; 
Console.WriteLine(DoubleConverter.ToExactString(f1)); 

float f2 = 16777217.0f; 
Console.WriteLine(DoubleConverter.ToExactString(f2)); 

Remarquez comment la représentation interne de 16.777.216,0 est le même 16.777.217,0! !