Il computer non è in grado di trattare i numeri reali ma solo una loro approssimazione.

È quello che fa ognuno di noi quando deve usare il "pi.greco"... mica pensiamo di usare molto più di 5 o 6 cifre decimali quando calcoliamo la lunghezza di una circonferenza? 

Il numero delle cifre decimali di "pi.greco" è infinito e questo significa che se lo si volesse usare completo non si avrebbe né abbastanza carta, né abbastanza memoria, né abbastanza tempo per eseguire i calcoli per cui lo dobbiamo comunque approssimare alla lunghezza che ci fa comodo o ad un valore che sia significativo per la precisione del risultato che ci aspettiamo.

Il computer deve fare la stessa cosa: approssimare visto che anche "lui" ha una memoria di dimensioni limitate da assegnare a ciascun numero.

In aggiunta il computer fa altre approssimazioni in quanto al suo interno usa numeri in base 2 e non in base dieci.

La rappresentazione dei numeri entro un computer avviene in base due con notazione in virgola mobile che consiste nel conservare un certo numero di cifre significative (talvolta chiamata mantissa o "significante") moltiplicata per una potenza del 2 per definire l'ordine di grandezza).

Analogamente si può scrivere un numero decimale come prodotto di una cifra decimale moltiplicata per una potenza del 10.

Scratch prova ad utilizzare il massimo delle cifre significative ammesse nel visore senza ricorrere alla forma esponenziale, se il numero eccede lo spazio del visore allora ricorre alla forma esponenziale.

La sistematizzazione di tutti i problemi connessi con la rappresentazione dei numeri in un computer è fissata nello standard "IEEE 754".

 

numeMacchina3Nota: le prove che seguono sono state effettuate con la versione 3 di Scratch ed i risultati descritti fanno riferimento ad essa.

L'applicazione di Scratch "numeri macchina" è stata creata per sondare almeno in parte i limiti di memorizzazione di un computer.

Alla sua apertura con "bandierina verde" sono disponibili numerosi pulsanti software per creare rapidamente numeri molto grandi o molto piccoli per mezzo di moltiplicazioni e divisionni a partire dal numero "1", fissato per default.

Sullo stage compaiono i valori delle variabili

"x0" è il valore immesso per iniziare le operazioni,

"x" è il risultato del calcolo effettuato con i pulsanti moltiplicatori e divisori,

"x*10^12" è lo stesso risultato ma 1000 miliardi di volte più grande per permettere di osservare le cifre meno significative altrimenti eliminate dal visore di Scratch con approssimazioni fissate dai progettisti.

 

L'atteggiamento più produttivo è quello di provare con numeri diversi e osservare cosa accade modificando il valore di X scelto a piacere.

Con i pulsanti arancione si eseguono moltiplicazioni del valore di "x" per il fattore indicato sul pulsante e con i pulsanti verdi si eseguono divisioni per il divisore indicato sul pulsante. 

I risultati numerici del calcolo, che vengono salvati nellavariabile "x", vengono presentati tramite il visore numerico che ne fa un'approssimazione (l'argomento è stato affrontato in questo articolo).

In aggunta è disponibile una lista che registra i valori via via calcolati per permettere di rivedere come si è evoluto il risultato con operazioni successive; nella lista viene usata la notazione esponenziale per la rappresentazione dei numeri (la lista si mostra con [tasto M] e si nasconde con [tasto N]).

 

Un esempio

Dividendo il numero 1 per 1030, si vede che il numero "1" è diventato così piccolo che il risultato presentato (e sottolineo "presentato") è x=0 come vale zero lo stesso valore 1000 miliardi di volte più grande indicato con "x*10^12".

La cella corrispondente della lista (la riga 2) contiene:

x0*10^-30: x = 9.999999999999999e-31

 

significa che il valore iniziale x0=1 diviso per 10^30 

pone "x" al valore 

x=0.0000000000000000000000000000009999999999999999

Il visore ci fa vedere "0" e i progettisti hanno fatto bene a fare così, in fondo Scratch è stato pensato per i bambini.

 

Per qualche ragione è andata persa la cifra significtiva "1" in favore di una sua approssimazione: Scratch non mantiene le cifre esatte così come sono state inserite.

Se si procede per gradi dividendo il numero 1 per 10 un po' di volte si vede che già alla sesta iterazione sono state aggiunte altre cifre sigificative; il numero non è più identico all'originale, cioé il valore in memoria risulta corrotto, basta gardareil valore della variabile "x*10^12" che è 0.0000010000000000000002.

La corruzione del numero sembra minima ma se si esegue un test con l'istruzione "se (x=0,000001), allora... altrimenti..." si vede che il risultato del test è "falso" in quanto il valore di "x", quello contenuto nella memoria del PC, è vicinissimo a 0,000001 da cui differisce solo alla 16^ cifra significativa ma non è esattamente "0.000001".

 

Esperimenti

Con il valore di default "1" provare a moltiplicare o dividere per numeri moto grandi.

Si vede che:

- moltiplicando 1 diverse volte per 10 il visore "x" arriva a rappresentare numeri fino a 20 cifre intere prima di passare alla notazione esponenziale,

- dividendo 1 sei volte per 10 il visore "x" arriva a mostrare sei cifre decimali ma alla settima volta mostra zero;

- moltiplicando 1 per 10^30  entrambi i visori passano allanotazione esponanziale; se si moltiplica per altre dieci volte si arriva ad un valore così grande che il computer lo dichiara infinito, nella lista si vede che l'esponente ha superato il valore 300 (si tratta di un numero molto grande trattandosi di un 1 con 300 zeri prima della virgola);

- dividendo per 10^30 si vede 0 in entrambi i visori ma nella memoria c'è ancora un numero e lo si vede nella lista dei valori; ripetendo altre 9 volte la divisione per 10^30 si vede che l'esponente è diventato -301 (si tratta di un numero piccolissimo che ha 301 zeri dopo la virgola e prima di 1; ripetendo ancora una volta la divisione si vede che anche nell'ultima cella della lista si ha zero, il computer non ce la fa proprio a contenere un numero così piccolo.

Premendo [tasto X] si possono inserire altri valori di partenza in modo da poter indagare su numeri che si comportano in modo "particolare".

 

Premere [tasto 1] per verificare se dividere e poi moltiplicare per uno stesso numero si ottiene lo stesso numero iniziale.

numMacch1bIn questo test il numero 1 viene diviso 8 volte per 10 e poi moltiplicato altrettante volte per 10. Come risultato dovremmo avere lo stesso numero di partenza ed infatti sul visore si vede ancora il numero "1" ma le cose non stanno propriamente in questi termini.

Al termine dei calcoli viene effettuata una verifica per vedere se il risultato terminale è uguale al numero iniziale (figura a destra) e l'esito non è quello che ci si aspetta.

... in che senso?

Nel senso che il valore inizale "1" non esiste più ma c'è nella memoria solo una sua approssimazione nella quale la difrerenza potrebbe essere di una unità alla quindicesima cifra decimale dopo la virgola.

Per un computer può accadere che il valore di "x" mostrato uguale a 1 sul visore non sia esattamente uguale al valore che c'è in memoria per quanto esso sia molto prossimo a 1 ... senza però essere 1.

Se questo valore è stato oggetto di operazioni numeriche successive il risultato dopo ciascun calcolo viene approssimato alla cifra binaria più vicina ad ogni passo e rifacendo i calcoli inversi a ritroso, i valori persi nelle approssimazioni non si recuperano.

Il risultato è che qualche cifra, trascurabilissima per la sua insignificanza pratica, sia leggermente diversa da quanto ci si aspetterebbe da un computer "serio" ... se anche il computer sbaglia i calcoli, stiamo freschi!

Il fatto è che il computer è "serissimo" solo che non può fare più di quanto i suoi limiti di memoria ed il calcolo binario possano permettergli di fare.

Il computer è stato costruito per manipolare numeri binari di lunghezza limitata che necessitano di approssimazioni per la loro memorizzazione e poi di conversione per la loro visualizzazione decimale.

 

Altre prove sulla visualizzazione dei numeri

[tasto 2]: test per verificare che ripetere per 10 volte la somma x:= x+0,1 a partire da x=0 non da' "1";

[tasto 3]: test per verificare che ripetere per 8 volte la somma x:= x+0,125 a partire da x=0 da' "1"

[tasto 4]: test per verificare che ripetere per 64 volte la somma x:= x+0,015625 a partire da x=0 da' "1" 

Perchè questa disparità di trattamento?

0,125 e 0,015625 sono numeri decimali memorizzati esattamente perchè sono sottomultipli di 2,

0,1 è un numero decimale non sottomultiplo di 2 per cui viene approssimato dalla macchina con la numerazione binaria "al meglio" ma non esattamente uguale ed infatti il test da un risultato "falso".

Questa approssimazione è "fatale" nei test di uguagliuanza ma nei calcoli tecnici e scientifici non costituisce un problema.

Inserire nel programma di Scratch condizioni del tipo "se un (valore) è uguale ad un numero" e non si è sicuri che tali valori possano essere identici nella memoria in tutte le sue cifre, anche quelle non visibili (per tutte si intende sia l'esopnente che la parte significativa) ci si potrebbe trovare in una situazione di errore computazionale con conseguenze sullo sviluppo successivo del programma.

 

Cifre significative

Si imposta il seguente esperimento:

- si mette in memoria un numero intero "x" a una cifra;

- si esegue un test per verificafre se x+1 è uguale a x: i due valori sono evidentemente diversi ed il test <x+1=x> non può che essere falso, almeno così sembra (esempio, si pone x= 6 per cui ed il test <x+1=x> chiede se 7=6 ... evidentemente no ed infatti il risultato del test è "falso");

- si moltiplicano entrambe le variabili per 10 e si ripete il test (esempio, ora x= 60 ed il test <x+1=x> diventa 61=60? ed il risultato è "falso, tutto normale;

- si ripete la oltiplicazione per 10 tante volte fino a quanto accade una cosa "strana": pur aggiungendo 1 a x accade che x+1=x è vera (questo succede dopo sedi volte con il test <x+1=x> dove si verifica che il confronto fra 60000000000000000+1 e 60000000000000000> è vero.

L'esperimento schematizzato sopra viene avviato con [tasto P]. Dopo aver inserito il numero si producono le iterazioni di calcolo premendo [tasto C].

Ad un certo punto la circostanza <x+1=ref> diventa vera è il ciclo si interrompe mostrando il numero di iterazioni effettuate per arrivare a quel punto.

Cosa è accaduto dopo la sedicesima iterazione?

Una addizione alla diciasettesima cifra non è efficace perchè Scratch non tratta più di 16 cifre significative ignorando quelle ancora meno significative.

 

Ecco alcuni articoli che sviluppano rigorosamente il problema (che non è di Scratch ma dei computer):

"Aritmetica di macchina e analisi dell'errore" (uniba) di Cinzia Elia e Felice Iavernaro,

"Numeri di macchina" (diapositive) di Luca Zanni e Marco Prato,

"Rappresentazione di numeri in macchina. Condizionamento e stabilità" (polito) di Stefano Berrone.

"Rappresentazioni" in virgola mobile uniroma2 Valeria Cardellini.

"Numeri macchina " di unipd.

 

note

nota 1: sono 16 le cifre decimali significative.

nota 2: ogni valore inserito viene trattato inizialmente come una stringa, solo dopo un'operazione Scratch "capisce" se deve trattarlo come un numero o come una stringa. È stata inserita una procedura di controllo della virgola decimale ma è bene prendere l'abitudine di utilizzare il punto decimale.

nota 3: il numero, scritto diversamente, è: 0,988*10-323. Sembra sia possibile scendere a numeri con esponente minore di -308 come previsto dalo standard IEEE 754. Ma se c'erano 16 cifre a disposizione per la mantissa e ne sono state corrotte 15 si è dovuti passare da esponente -308 ad esponente -308-15= -323 prima di vedere scomparire l'ultima cifra superstite del numero originale. Notare che subito dopo anche Scratch si "rifiuta" di continuare a dividere per 10 ponendo il valore a 0 anche se "appaiono" delle cifre non nulle ma che sappiamo casuali.