2010-02-08 35 views
15

Il seguente codice in C# (.Net 3.5 SP1) è un ciclo infinito sulla mia macchina:C# galleggiano ciclo infinito

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

ha raggiunto il numero di 16.777.216,0 e 16.777.216,0 + 1 è viene valutata a 16.777.216,0. Eppure a questo punto: i + 1! = I.

Questa è una follia.

Mi rendo conto che ci sono alcune imprecisioni nel modo in cui i numeri in virgola mobile vengono memorizzati. E ho letto che interi numeri superiori a 2^24 di quelli che non possono essere memorizzati correttamente come un float.

Ancora il codice sopra, dovrebbe essere valido in C# anche se il numero non può essere rappresentato correttamente.

Perché non funziona?

È possibile ottenere lo stesso risultato per il doppio ma richiede molto tempo. 9007199254740992.0 è il limite per il doppio.

+7

Perché stai utilizzando un tipo a virgola mobile per un indice in primo luogo? – John

+0

Sono d'accordo che non è un buon codice, ma non dovrebbe essere il codice corretto? Tecnicamente qualsiasi numero più uno dovrebbe essere maggiore di se stesso a meno che non ci sia un overflow. – jonathanpeppers

+1

Non necessariamente. Noterai che '16777216.0' è il float a precisione singola più vicino a '16777217.0' –

risposta

21

destro, quindi il problema è che, al fine di aggiungere uno al galleggiante, avrebbe dovuto diventare

16777217.0 

Si dà il caso che questo è in un contorno per la radice e non può essere rappresentato esattamente come un galleggiante. (Il prossimo valore più alto disponibile è 16777218.0)

Quindi, arrotonda al galleggiante rappresentabile più vicino

16777216.0 

Mettiamola in questo modo:

Dal momento che si dispone di un galleggiante quantità di precisione , devi aumentare di un numero sempre più alto.

EDIT:

Ok, questo è un po 'difficile da spiegare, ma provate questo:

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

Questo verrà eseguito bene, perché a quel valore, al fine di rappresentare un differenza di 1.0f, avresti bisogno di oltre 128 bit di precisione. Un float ha solo 32 bit.

EDIT2

Secondo i miei calcoli, almeno 128 cifre binarie senza segno sarebbe necessario.

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

Come soluzione al problema, è possibile eseguire il ciclo di due numeri a 128 bit. Tuttavia, questo richiederà almeno un decennio per essere completato.

+0

C# dovrebbe consentirne l'incremento, sebbene sia giusto? Qualsiasi float + 1 dovrebbe essere maggiore del float stesso, giusto? i + 1> i in ogni caso? Direi altrimenti se ci fosse un overflow, ma il numero non è nemmeno vicino a float.MaxValue. – jonathanpeppers

+6

@ Jonathan.Peppers: Non sono sicuro di dove abbiate questa idea sbagliata sui sistemi di numeri in virgola mobile. Ti suggerisco di leggere su di loro su wikipedia, quindi rivalutare il tuo codice. http://en.wikipedia.org/wiki/Floating_point – Welbog

+3

Ok, pensa in questo modo: Floats ha un numero di cifre impostato. Vicino a 'float.MaxValue', il valore non è preciso. In effetti, la maggior parte delle cifre insignificanti viene troncata. Prova a sottrarre uno da float.MaxValue. –

-4

L'iterazione quando mi avvicino a float.MaxValue è appena al di sotto di questo valore. La prossima iterazione si aggiunge a i, ma non può contenere un numero più grande di float.MaxValue. Quindi mantiene un valore molto più piccolo e ricomincia il ciclo.

+0

Il codice non sta straripando ... – TJMonk15

6

di capire cosa sta andando male si sta andando ad avere per leggere lo standard IEEE su floating point

Esaminiamo la struttura di un certo numero floating point per un secondo:

Un numero in virgola mobile è suddiviso in due parti (ok 3, ma ignora il bit del segno per un secondo).

Hai un esponente e una mantissa. In questo modo:

smmmmmmmmeeeeeee 

Nota: questo non è acurate al numero di bit, ma ti dà un'idea generale di ciò che sta accadendo.

di capire che cosa numero che avete facciamo il seguente calcolo:

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

Allora, qual è float.MaxValue sarà? Bene, avrai la più grande mantissa possibile e il più grande esponente possibile. Facciamo finta che questo sembra qualcosa di simile:

01111111111111111 

in realtà definiamo Nan e + -INF e un paio di altre convenzioni, ma li ignorano per un secondo perché non sono rilevanti per la tua domanda.

Quindi, cosa succede quando hai 9.9999*2^99 + 1? Bene, non hai abbastanza cifre significative da aggiungere 1. Di conseguenza viene arrotondato allo stesso numero. Nel caso di singola precisione in virgola mobile il punto in cui +1 inizia a essere arrotondato sembra essere 16777216.0

8

Immaginiamo per esempio che un numero decimale è rappresentato da un massimo di 2 cifre decimali significative, più un esponente: in tale caso, potresti contare esattamente da 0 a 99. Il prossimo sarebbe 100, ma perché si possono avere solo 2 cifre significative che verrebbero memorizzate come "1.0 volte 10 alla potenza di 2". Aggiungere uno a quello sarebbe ... cosa?

Al massimo, sarebbe 101 come risultato intermedio, che verrebbe effettivamente memorizzato (tramite un errore di arrotondamento che scarta la 3a cifra non significativa) come "1,0 volte 10 alla potenza di 2" di nuovo.

2

Non ha nulla a che fare con l'overflow o vicino al valore massimo. Il valore float per 16777216.0 ha una rappresentazione binaria di 16777216. Quindi lo si incrementa di 1, quindi dovrebbe essere 16777217.0, con la differenza che la rappresentazione binaria di 16777217.0 è 16777216 !!! Quindi in realtà non viene incrementato o almeno l'incremento non fa quello che ti aspetti.

Ecco una classe scritta da Jon Skeet che illustra questo:

DoubleConverter.cs

provare questo codice con esso:

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)); 

Notate come la rappresentazione interna di 16.777.216,0 è lo stesso 16.777.217,0! !

Problemi correlati