Domanda

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

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

Ha raggiunto il numero 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 certa pazzia.

Mi rendo conto che v'è una certa imprecisione nel modo in cui i numeri in virgola mobile sono memorizzati. E ho letto che i numeri interi maggiore 2 ^ 24 che non può essere correttamente conservato come un galleggiante.

Ancora il codice di cui sopra, dovrebbe essere valido in C #, anche se il numero non può essere rappresentato in modo corretto.

Perché non funziona?

È possibile ottenere lo stesso accada per il doppio, ma ci vuole un tempo molto lungo. 9007199254740992,0 è il limite per il doppio.

È stato utile?

Soluzione

A destra, in modo che il problema è che, al fine di aggiungere uno al galleggiante, avrebbe dovuto diventare

16777217.0

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

Quindi, si arrotonda al galleggiante rappresentabile più vicino

16777216.0

Mettiamola in questo modo:

Dal momento che si dispone di un galleggiante quantità di precisione, è necessario incrementare da un-e alto-alto numero.

Modifica

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 una differenza di 1.0f, si avrebbe bisogno di oltre 128 bit di precisione. Un galleggiante ha solo 32 bit.

EDIT2

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

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

Come soluzione al tuo problema, si potrebbe scorrere due a 128 numeri di bit. Tuttavia, questo richiederà almeno un decennio per essere completata.

Altri suggerimenti

Immaginiamo per esempio che un numero in virgola mobile è rappresentato da un massimo di 2 cifre decimali significative, oltre a un esponente: in quel caso, si poteva contare da 0 a 99 esattamente. Il prossimo sarebbe 100, ma perché si può avere solo 2 cifre significative che sarebbero memorizzati come "1.0 volte 10 alla potenza di 2". L'aggiunta di uno a che sarebbe ... che cosa?

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

Per capire cosa sta andando male si sta andando ad avere per leggere lo standard IEEE su virgola mobile

Esaminiamo la struttura di un punto numero galleggiante per un secondo:

Un numero a virgola mobile è suddiviso in due parti (ok 3, ma ignorare il bit di segno per secondo).

Si dispone di un esponente e una mantissa. In questo modo:

smmmmmmmmeeeeeee

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

Per capire quale numero avete facciamo il seguente calcolo:

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

Allora, cosa sta float.MaxValue sarà? Ebbene si sta andando ad avere la massima 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 ignoriamo per un secondo perché non sono rilevanti per la tua domanda.

Quindi, cosa succede quando hai 9.9999*2^99 + 1? Beh, non si dispone di cifre abbastanza significativo per aggiungere 1. Come risultato si ottiene arrotondato per difetto allo stesso numero. Nel caso di singola precisione in virgola mobile il punto in cui inizia +1 ottenere arrotondato sembra essere 16777216.0

Non ha nulla a che fare con troppo pieno, o di essere vicino al valore massimo. Il valore galleggiante per 16.777.216,0 ha una rappresentazione binaria di 16777216. Quindi incrementarlo di 1, quindi dovrebbe essere 16.777.217,0, salvo che la rappresentazione binaria di 16.777.217,0 è 16777216 !!! Quindi in realtà non ottiene incrementato o almeno l'incremento non fa quello che ci si aspetta.

Ecco una classe scritta da Jon Skeet che illustra questo:

DoubleConverter.cs

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

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

L'iterazione quando mi si avvicina float.MaxValue ha ho appena al di sotto di questo valore. La prossima iterazione aggiunge i, ma non può contenere un numero maggiore di float.MaxValue. Così contiene un valore molto più piccolo, e ricomincia il ciclo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top