2009-04-15 11 views
8

Perché il seguente programma stampa ciò che stampa?Perché l'aritmetica in virgola mobile in C# è imprecisa?

class Program 
{ 
    static void Main(string[] args) 
    { 
     float f1 = 0.09f*100f; 
     float f2 = 0.09f*99.999999f; 

     Console.WriteLine(f1 > f2); 
    } 
} 

uscita è

false 
+0

Perché non lo provi solo per te? –

+10

Ho provato, voglio solo sapere perché vedo quello che vedo. – Prankster

+1

[non applicabile alla lingua: la matematica in virgola mobile è rotta?] (Http://stackoverflow.com/q/588004/995714) –

risposta

30

virgola mobile ha solo tante cifre di precisione. Se vedi f1 == f2, è perché qualsiasi differenza richiede più precisione di quanto possa rappresentare un float a 32 bit.

vi consiglio di leggere What Every Computer Scientist Should Read About Floating Point

+0

+1 Perché, dopo aver premuto il pulsante Invia, perché qualcuno ha pubblicato i collegamenti? : P – dirkgently

+1

+1 http://msdn.microsoft.com/en-us/library/b1e65aza(VS.71).aspx Michael è corretto, C# float ha solo 7 cifre di precisione e 99.999999f ha 8 cifre. – James

+6

Questo non è specifico per C#. Un float IEEE-754 a precisione singola sarà solo a 32 bit, che fornisce circa 7 cifre decimali di precisione. Se vuoi di meglio, usa un doppio. – Wedge

5

La cosa principale è che questo non è solo .Net: si tratta di una limitazione del underlying system most every language will use to represent a float in memoria. La precisione arriva solo così lontano.

Puoi anche divertirti con numeri relativamente semplici, tenendo conto che non è nemmeno la base dieci. 0.1, ad esempio, è un decimale ricorrente quando rappresentato in binario.

3

In questo caso particolare, è perché .09 e .999999 non possono essere rappresentati con precisione esatta in binario (analogamente, 1/3 non può essere rappresentato con precisione esatta in decimale). Ad esempio, 0.11111111111111111111101111 base 2 è 0.999998986721038818359375 base 10. Aggiungendo 1 al precedente valore binario, 0.11111111111111111111 base 2 è 0.99999904632568359375 base 10. Non esiste un valore binario esattamente 0.999999. La precisione del punto mobile è inoltre limitata dallo spazio allocato per la memorizzazione dell'esponente e della parte frazionaria della mantissa. Inoltre, come i tipi di interi, il punto in virgola mobile può traboccare il suo intervallo, sebbene il suo intervallo sia maggiore degli intervalli di numeri interi.

esecuzione di questo pezzo di codice C++ nel debugger Xcode,

float myFloat = 0,1;

indica che myFloat ottiene il valore 0,100000001. È disattivato da 0,000000001. Non molto, ma se il calcolo ha diverse operazioni aritmetiche, l'imprecisione può essere aggravata.

IMHO una buona spiegazione di virgola mobile è nel capitolo 14 di Introduzione alla Organizzazione del computer con x86-64 Assembly Language & GNU/Linux da Bob Plantz della California State University at Sonoma (in pensione) http://bob.cs.sonoma.edu/getting_book.html. Quanto segue si basa su quel capitolo.

Il punto di virgola mobile è come una notazione scientifica, in cui un valore è memorizzato come un numero misto maggiore o uguale a 1.0 e inferiore a 2.0 (la mantissa), moltiplicato per un altro numero (l'esponente). Il punto fluttuante usa la base 2 piuttosto che la base 10, ma nel modello semplice che dà Plantz, usa la base 10 per chiarezza. Immagina un sistema in cui due posizioni di memorizzazione vengono utilizzate per la mantissa, una posizione viene utilizzata per il segno dell'esponente * (0 rappresenta + e 1 che rappresenta -) e una posizione viene utilizzata per l'esponente. Ora aggiungi 0.93 e 0.91. La risposta è 1.8, non 1.84.

9311 rappresenta 0,93, o 9,3 volte 10 a -1.

9111 rappresenta 0,91 o 9,1 volte 10 a -1.

La risposta esatta è 1.84, o 1.84 volte 10 allo 0, che sarebbe 18400 se avessimo 5 posizioni, ma, avendo solo quattro posizioni, la risposta è 1800, o 1.8 volte 10 allo zero, o 1.8 . Naturalmente, i tipi di dati in virgola mobile possono utilizzare più di quattro posizioni di archiviazione, ma il numero di posizioni è ancora limitato.

Non solo la precisione è limitata dallo spazio, ma "una rappresentazione esatta dei valori frazionari in binario è limitata a somme di poteri inversi di due". (Plantz, op.cit.).

,11,10011 milione (binario) = ,89,84375 milione (decimale)

0,11100111 (binario) = ,90,234375 millions (decimale)

Non c'è rappresentazione esatta di 0,9 decimale in binario. Anche portare la frazione fuori più posti non funziona, come si arriva a ripetere 1100 per sempre sulla destra.

I programmatori principianti spesso vedono l'aritmetica in virgola mobile quanto più precisi di numero intero. È vero che persino l'aggiunta di due interi molto grandi può causare un overflow. La moltiplicazione rende ancora più probabile che il risultato sarà molto grande e, quindi, overflow. E quando si utilizza con due numeri interi, l'operatore/in C/C++ causa la perdita della parte frazionaria . Tuttavia, ... le rappresentazioni a virgola mobile hanno il loro set di imprecisioni . (Plantz, op. Cit.)

* In virgola mobile sono rappresentati sia il segno del numero sia il segno dell'esponente.

Problemi correlati