venerdì 26 ottobre 2007

Virgola mobile e precisione

Sicuramente vi è capitato almeno una volta nella vostra vita programmativa di lavorare con tipi in virgola mobile. Ache a me è capitato, ma sinceramente mai ho fatto caso a una cosa che in prima apparenza sembrava stranissima, poi inrealtà si è verificata normale.
Provate a scrivere il seguente frammento di codice in una classe c#:
Single c = 12;
Single d = 12.00f;
Single de = 12.23f;
Single r = 12.1f;

se poi fate visualizzare a schermo i valori, vengono visualizzati correttamente, ossia
System.Console.WriteLine(c.ToString());
System.Console.WriteLine(d.ToString());
System.Console.WriteLine(de.ToString());
System.Console.WriteLine(r.ToString());

Ottenete esattamente i valori che vi interessano.

Provate però a fare il debug passo passo e fermarvi su ogni singolo valore:

Beh certo i valori non sono quelli che avete inserito voi. Ma da cosa dipende questa cosa?

DA MSDN:
"Single-precision numbers store an approximation of a real number
When you work with floating-point numbers, keep in mind that they do not always have a precise representation in memory"

Diamo due articoli come lettura su questo argomento:

http://msdn2.microsoft.com/en-us/library/system.single.aspx
http://msdn2.microsoft.com/en-us/library/system.single(VS.80).aspx
presi direttamente da MSDN, la Bibbia della programamzione in .NET

Il problema fondamentale sta nel fatto che il Single (float), come il Double (double) sono tipi di dato a virgola mobile. Ciò vuol dire che non hanno una precisa rappresentazione all'interno del computer, perchè come si dovrebbe ben sapere, tra un numero e l'altro ne esistono infiniti e infinito putroppo :( è un valore che il computer non capisce. La virgola mobile allora (come del resto anche la virgola fissa) cerca di approssimare questo numero a un determinato numero di cifre decimali: float 6, double 15. Se ci facciamo caso infatti i valori visualizzati dal debug sono 12.0 che è corretto poichè è preciso 12.2299995 che approssimato viene 12.23 infatti l'approssimazione viene fatta sull'ultima cifra decimale che in questo caso è la settima e il Single ne contiene solo 6 quindi si può dire con ottima approssimazione che il numero registrato è 12.23 dato che per il calcolatore quello è il più vicino numero registrabile a quello reale. Ad esempio se inseriremmo 12.229999 il computer avrà sicuramente in memoria 12.2299990.

Ma come mai il computer si complica così la vita? Non potrebbe stare semplicemente a registrare il valore che io gli inserisco senza fare a modo suo per registrare? Beh la risposta risiede nel metodo di immagazzinamento a virgola mobile che viene usato nel compilatore in base 2.

Da Wikipedia:
floating-point
Un generico numero reale a può così essere rappresentato come (si indica con le lettere maiuscole il significato aritmetico dei campi):

Questo metodo di scrittura permette di rappresentare un larghissimo insieme numerico all'interno di un determinato numero di cifre, cosa che la virgola fissa non concede.

In generale, questo tipo di numeri si comporta in modo molto simile ai numeri reali. Tuttavia ciò porta spesso i programmatori a non considerare l'importanza di un'adeguata analisi numerica risultati ottenuti. Ci sono molte incongruenze tra il comportamento dei numeri in virgola mobile in base 2 impiegati nell'informatica e quello dei numeri reali, anche in casi molto semplici (ad esempio la frazione 0,1 che non può essere rappresentata da nessun sistema binario in virgola mobile). Per questo non è un formato impiegato ad esempio in campo finanziario.

Le cause principali di errore nel calcolo in virgola mobile sono:

  • arrotondamento
    • numeri non rappresentabili (ad esempio 0,1);
    • arrotondamento di operazioni aritmetiche (es.: 2/3=0,666667);
  • assorbimento (es.: 1×1015 + 1 = 1×1015);
  • cancellazione (es.: sottrazione di due numeri molto vicini);
  • overflow (con segnalazione di risultato infinito);
  • underflow (dà come risultato 0, un numero subnormale o il più piccolo numero rappresentabile);
  • operazioni impossibili (es.: radice quadrata di un numero negativo diverso da zero, danno come risultato Nan);
  • errori di arrotondamento: a differenza della virgola fissa, l'impiego del dithering sulla virgola mobile è pressoché impossibile.

La virgola mobile appare più appropriata quando si richiede una certa precisione relativa al valore. Quando è richiesta una precisione assoluta, la virgola fissa sembra una scelta migliore.


Riguardo all'errore di precisione che provoca l'utilizzo della virgola mobile, innanzitutto notiamo che se x è il numero rappresentato, cioè fl(x)=segno(x)(0.a1a2...an)bexp(p) allora si avrà 1/b*bexp(p)<=|x|=bexp(1-p) allora 1/|x|<=bexp(p-1) e quindi bexp(p-t)*bexp(1-p)=bexp(1-t). In particolare l'errore relativo è variabile ma sempre al di sotto del valore trovato(che infatti non dipende da p e quindi non dipende dal numero rappresentato, come invece fa l'errore relativo) che viene anche detto precisione macchina!

In conclusione bisogna stare attenti a usare la virgola mobile, ma a meno che non vi servano calcoli con cifre esatte fino alle 154106410489046 posizione decimale, il .NET provvede per voi.

Nessun commento: