Subsections


B. La parte aritmetica in virgola mobile: problemi e limiti

Nella parte meccanica dei computer i numeri in virgola mobile si presentano come frazioni in base 2 (binarie): per esempio, il numero razionale in base 10

0.125

ha valore 1/10 + 2/100 + 5/1000 e parimenti il numero razionale in base 2

0.001

ha valore 0/2 + 0/4 + 1/8. Entrambi hanno valore identico, la sola differenza è la base del loro sistema di numerazione. Il primo scritto in base 10 ed il secondo in base 2.

Purtroppo, la maggior parte dei numeri razionali decimali non può essere tradotta in base binaria, con la conseguenza che, in genere, i decimali a virgola mobile inseriti sono solo approssimati nei numeri binari in virgola mobile inclusi nella macchina.

Si può comprendere meglio il problema considerando prima la base 10; si prenda la frazione 1/3: essa può essere approssimata nel numero razionale decimale:

0.3

o, meglio,

0.33

o, meglio,

0.333

e così via. Non si arriverà mai al risultato esatto di 1/3, ma, per ogni cifra aggiuntiva, ogni approssimazione sarà progressivamente migliore.

Allo stesso modo, non si riuscirà a rappresentare esattamente il decimale 0,1 traducendolo in base 2, dacché se ne ottiene il numero periodico infinito

0.0001100110011001100110011001100110011001100110011...

L'approssimazione si ottiene fermandosi a qualunque numero finito di cifre e per questo si trovano espressioni come:

>>> 0.1
0.10000000000000001

Questo si otterrebbe solo sulla maggior parte delle macchine se si scrivesse 0,1 al prompt di Python - non sempre, perché il numero di bit usati dalle componenti fisiche, per immagazzinare valori in virgola mobile, può variare da una macchina all'altra. Inoltre, Python visualizza un'approssimazione decimale del vero valore del numero binario immagazzinato dalla macchina. Per lo più, se dovesse visualizzare il vero valore decimale dell'approssimazione binaria di 0,1, dovrebbe far apparire, invece:

>>> 0.1
0.1000000000000000055511151231257827021181583404541015625

Per ottenere una versione sotto forma di stringa di ciò che fa apparire, il prompt di Python utilizza (implicitamente) la funzione built-in repr(); con virgola mobile repr(float), arrotonda il vero valore decimale a 17 cifre significative:

0.10000000000000001

repr(float) produce 17 cifre, significative in quanto sufficienti (sulla maggior parte delle macchine) per far sì che eval(repr(x)) == x sia valido per tutte le variabili finite di x - questo non avverrebbe, arrotondando a 16 cifre.

Si noti che questo non è un difetto di Python, né del proprio codice, ma risponde alla vera natura dei binari in virgola mobile: anzi, si ripresenta con tutti i linguaggi che supportino l'aritmetica in virgola mobile dei componenti elettronici (anche se taluni non visualizzano in modo predefinito la differenza, o, almeno, in tutte le modalità).

La funzione built-in str() produce solo 12 cifre significative, ma la si potrebbe preferire: è raro far riprodurre x a eval(str(x)); il risultato potrebbe essere più gradevole nella forma:

>>> print str(0.1)
0.1

Si badi: propriamente, questa è un'illusione; il vero valore di macchina non è esattamente 1/10, ma, semplicemente, lo si arrotonda per la visualizzazione.

E le sorprese non finiscono qui: per esempio, vedendo

>>> 0.1
0.10000000000000001

si potrebbe essere tentati di usare la funzione round() per ridurre tutto a una singola cifra, ma ciò non apporta alcun cambiamento:

>>> round(0.1, 1)
0.10000000000000001

Il problema è che il valore binario in virgola mobile immagazzinato per "0,1" è già la migliore approssimazione in base 2 di 1/10 e va già bene così, inutile cercare di arrotondarla.

Un'altra conseguenza: poiché la traduzione di 0,1 non corrisponde esattamente a 1/10, sommare 0,1 per dieci volte non dà esattamente 1.0:

>>> sum = 0.0
>>> for i in range(10):
...     sum += 0.1
...
>>> sum
0.99999999999999989

Sorprese come questa sono numerose nell'aritmetica binaria in virgola mobile. Sul problema con "0,1" cfr. si veda la spiegazione in dettaglio, nella sezione "Errore di rappresentazione" e I rischi della virgola mobile, resoconto completo sulle altre frequenti singolarità.

Alla fin fine, ``non ci sono risposte semplici'': ciononostante, inutile preoccuparsi troppo! Gli errori nelle operazioni in virgola mobile con Python sono dovuti a impostazioni strutturali della parte meccanica e, per la maggioranza delle macchine, hanno una frequenza di circa 1 su 2**53 - più che adeguata, dunque. Bisogna ricordare, però, che non è artimetica decimale, per cui ogni operazione in virgola mobile può soffrire di errori di arrotondamento.

Quindi: casi patologici esistono, ma negli usi più comuni dell'aritmetica in virgola mobile si otterranno i risultati desiderati arrotondando semplicemente la visualizzazione del risultato finale a un certo numero di cifre decimali. Di solito basta str(), ma, per un controllo più preciso, cfr. la discussione sull'operatore di formato % di Python: i codici di formato %g, %f ed %e offrono sistemi semplici e flessibili per visualizzare risultati arrotondati.


B.1 Errore di rappresentazione

Questa sezione spiega in dettaglio l'esempio di ``0,1'' e mostra come condurre da sé un'analisi esatta di casi analoghi. Si dà per scontata una certa familiarità con la rappresentazione in virgola mobile in base 2.

L'errore di rappresentazione si riferisce all'impossibilità di rappresentare esattamente come numeri razionali binari la maggior parte (almeno per ora) dei numeri razionali decimali. Questa è la ragione principale per cui Python (come Perl, C, C++, Java, Fortran e molti altri) non visualizza il numero che ci si aspetterebbe:

>>> 0.1
0.10000000000000001

Qual è il motivo? 1/10 non è esattamente traducibile in base 2. Oggi (novembre 2000) quasi tutte le macchine usano un'aritmetica in virgola mobile di tipo IEEE-754 e quasi tutte le piattaforme riportano i numeri in virgola mobile di Python alla "doppia precisione" di tipo IEEE-754. 754 doppi contengono 53 bit di precisione, per cui a comando il computer tende a convertire 0,1 al numero razionale binario più vicino alla forma J/2**N, dove J è un intero contenente esattamente 53 bit. Riscrivendo

 1 / 10 ~= J / (2**N)

nella forma

J ~= 2**N / 10

e tenendo sempre presente che J ha 53 bit (quindi è >= 2**52 ma < 2**53), il miglior valore di trascrizione per N sarà 56:

>>> 2L**52
4503599627370496L
>>> 2L**53
9007199254740992L
>>> 2L**56/10
7205759403792793L

In altre parole, 56 è il solo valore di N ad ammettere un J con 53 bit, il cui miglior valore possibile sarà, quindi, il quoziente arrotondato:

>>> q, r = divmod(2L**56, 10)
>>> r
6L

Dal momento che il resto è maggiore di 10/2, la migliore approssimazione si otterrà arrotondando per eccesso:

>>> q+1
7205759403792794L

Perciò, la migliore approssimazione di 1/10 nella doppia precisione di tipo 754 è quella maggiore di 2**56, ovvero

7205759403792794 / 72057594037927936

Si badi: siccome abbiamo arrotondato per eccesso, questo valore è effettivamente un po' più grande di 1/10; in caso contrario sarebbe stato leggermente inferiore, ma mai in ogni caso uguale a 1/10!

In tal modo, il computer non ``vede'' mai 1/10, bensì il numero razionale che sia la migliore approssimazione possibile di tipo 754:

>>> .1 * 2L**56
7205759403792794.0

Moltiplicando tale numero razionale per 10**30, possiamo vedere il valore (tronco) delle sue 30 cifre decimali più significative:

>>> 7205759403792794L * 10L**30 / 2L**56
100000000000000005551115123125L

Ciò significa che il numero esatto memorizzato nel computer è uguale approssimativamente al valore decimale 0.100000000000000005551115123125, che, arrotondato alle sue 17 cifre significative, dà il famoso 0.10000000000000001 visualizzato da Python (o meglio, visualizzato su piattaforme conformi al tipo 754 e capaci, nella propria libreria di C, della migliore conversione possibile di dati in entrata e in uscita - non tutte lo sono!)

Vedete Circa questo documento... per informazioni su modifiche e suggerimenti.