Calcolatore (6) - I numeri macchina

#1  Precisione dei risultati
    Nella voce  strutture numeriche e non abbiamo visto che i numeri macchina di una CT costituiscono una struttura per vari aspetti diversa da quella dei numeri reali: è costituita da una quantità finita di elementi, ha operazioni diverse (12345678·0. 12345678 per la moltiplicazione tra numeri reali è pari a 1524157.65279684, per una CT che arrotonda a 8 cifre è pari a 1524157.7), non valgono alcune proprietà, anche elementari, come quella del riordino di una somma:
il calcolo di  –12345678 12345678 0. 12345678
e quello di   –12345678 0 .12345678 12345678
per una CT che arrotonda a 10 cifre non sono equivalenti:  mentre il primo dà 0 .12345678 il secondo dà 0 .12;  infatti -12345678+0 .12345678 (= -12345677.87654322) viene arrotondato a -12345677.88, che, sommato a 12345678, fa appunto 0 .12.
    Dunque, nell'eseguire un calcolo complesso con una CT è utile analizzare il termine da calcolare ed eventualmente trasformarlo, non solo per facilitare l'impostazione del calcolo (vedi le voci richiamate nell'indice alfabetico sotto "calcolatrice tascabile e trasformazione di formule"), ma anche per ottenere risultati con una migliore precisone.

#2  Cancellazione delle cifre
    Facciamo un altro esempio. Per calcolare (127·9721+0 .1234567-1234567)/3 posso battere:
127 9721 0 .1234567 1234567 3 .
    Con una CT a 10 cifre ottengo 0 .041, con una a 12 ottengo 0 .0411533333333.
    Qual è il risultato migliore? Il risultato è esattamente 0 .041? o 0 .0411533333333 è il suo arrotondamento a 12 cifre?
    Se man mano guardo sul visore anche i risultati intermedi, osservo che 127·9721 fa 1234567; quindi il termine è equivalente a 0 .1234567/3, che vale 0 .0411522333…: nessuna delle due precedenti conclusioni è corretta!
    Il fenomeno che è all'origine di questo apparentemente strano comportamento viene chiamato "cancellazione delle cifre":
  il risultato di 127·9721+0 .1234567, ossia 1234567.1234567, dalla CT a 10 cifre viene arrotondato a 1234567.123; nel calcolo della differenza tra 1234567.123 e 1234567, le cifre iniziali, uguali nei due numeri, si azzerano (1-1 fa 0, 2-2 fa 0, 3-3 fa 0, …), così che ottengo un valore 0 .123 con 3 sole cifre significative;
–  con la successiva divisone per 3 ottengo 0 .041, ma è illusorio considerarlo un risultato esatto in quanto è stato ottenuto dalla divisone di 0 .123, che era solo una approssimazione a 3 cifre.
    Analogamente, la CT a 12 cifre arrotonda 1234567.1234567 a 1234567.12345, nella differenza con 1234567 perde le cifre iniziali, calcola 0 .12345/3 e fornisce 0 .0411533333333; è illusorio considerarlo un arrotondamento corretto a 12 cifre in quanto è stato ottenuto dividendo un valore approssimato a 5 cifre.
    In realtà il fenomeno della "cancellazione delle cifre" (o, meglio, della perdita di cifre signficative) si verifica anche se i due numeri non hanno le stesse cifre iniziali, ma hanno comunque una differenza molto più piccola di loro, come nel caso di 999.98 e 1000 .01: se devo calcolare π(1000 .01-999.98) dove i due numeri sono valori approssimati, se con la CT ottengo 0 .09424778, è illusorio prendere 0 .094248 (arrotondamento a 5 cifre) come approssimazione del risultato in quanto il valore 0 .03 della differenza 1000 .01-999.98 ha 1 sola cifra significativa. Posso prendere solo 0 .09.
Nota. Le approssimazioni potrebbero essere fatte in modo più rigoroso utilizzando gli intervalli di indeterminazione ( calcolo approssimato). Ad esempio nell'ultimo caso:
- 1000 .01 sta per [1000 .005, 1000 .015]
- 999.98 sta per [999.975, 999.985]
- la loro differenza sta in [0 .02, 0 .04]
- il prodotto per π sta in [0 .06283…, 0 .1256…]
- quindi posso approssimare il risultato con [0 .062, 0 .126], o, meno precisamente, con [0 .06, 0 .13], o, meno precisamente, con 0 .09±0 .04.

#3  I numeri macchina dei linguaggi di programmazione e di altre applicazioni
    Con questa calcolatrice se eseguo  843.27−843.23  e  5555+.1251−5555  ottengo  0 .03999999999996362  e  0 .12510000000020227  invece che  0 .04  e  0 .1251.  Come mai?
    Se eseguo operazioni analoghe con R ottengo:
x <- 843.27; y <- 843.23; x-y; x <- 5555 .1251; y <- 5555; x-y
    0 .04       0 .1251

ma se eseguo i seguenti calcoli, che dovrebbero dare 0, ho:
0 .04-(843.27-843.23); 0 .1251-(5555+.1251-5555)
    3.638062e-14       -2.022826e-13

    Come mai?
    Vediamo, prima, che cosa accade in R. Le uscite vengono espresse con 6 cifre, ma i risultati sono memorizzati internamente con più cifre (circa 16), che possiamo esplorare con i seguenti comandi:
print(843.27-843.23, 16); print(5555+.1251-5555, 16)
   0 .03999999999996362       0 .1251000000002023

    Con la "calcolatrice" iniziale abbiamo avuto uscite analoghe: essa visualizza direttamente più cifre.

    Come mai accade ciò?
    A differenza di quanto avviene nelle usuali CT, che memorizzano i numeri in BCD, gran parte delle applicazioni per computer memorizzano i valori numerici nei registri associati alle variabili in forma binaria. Puoi rivedere come vengono trasformati in forma binaria i numeri interi qui; puoi anche vedere come avviene questa trasformazione nel caso dei numeri non interi. Ad ogni modo su ciò ritorniamo con una semplice applicazione a cui puoi accedere da qui.  Eccone una particolare uscita:

Divisione di m per n con m < n (numeri naturali in base dieci) e risultato in base a scelta.
Cliccando  [Fai un passo]  ottieni via via le cifre del risultato
m = n = base =
resto =    

    Il concetto di numero limitato (cioè numero con periodo 0) dipende dalla base di rappresentazione. 1/2, che in base dieci è 0 .5, in base due diventa 0 .1; ma 1/10, che in base dieci diventa 0 .1, in base due diventa il numero illimitato 0 .00011001100110011… in quanto non riesco ad esprimere 0 .1 come somma di una quantità finita di frazioni prese tra 1/2, 1/4, 1/16, 1/32, ... (che, scritti in base due, diventano 0 .1, 0 .01, 0 .001, 0 .0001, …).
    A questo punto ci è facile capire le uscite strane considerate all'inizio del paragrafo:  843.27 e 843.23 vengono internamente espressi in forma binaria e approssimati, viene fatta la differenza tra questi due numeri in base 2, e il risultato viene visualizzato in base dieci: 0 .03999999999996362 è il risultato che differisce da 0 .04 del valore decimale corrispondente alle cifre binarie che si sono perse.

    I programmi considerati riescono a rappresentare un numero finito di numeri. Avranno, quindi, anche un numero massimo.
    Con la calcolatrice considerata all'inizio del paragrafo il massimo numero che riesco a calcolare è 21024 (1024 è pari a 2 alla 10, ossia, in base 2, a 10000000000); mi viene dato 1.797693e+308 come risultato; se aumento di 1 l'esponente o di 0 .00001 la base mi viene segnalato un errore di overflow. In R l'errore viene segnalato se batto 2^1024; ottengo il valore 1.797693e+308 se batto 2^1023*1.999999999999999.

    Analogamente non posso rappresentare numeri comunque piccoli. Ad esempio con R mentre 2^-1074.999999999999 fornisce 4.940656e-324, se aggiungo un 9, 2^-1074.9999999999999 dà 0. Questo fenomeno si chiama underflow. Esso non viene segnalato dal computer. Sta all'utente interpretare opportunamente le uscite (e prestare attenzione nella messa a punto di programmi per l'esecuzione di alcuni algoritmi).

    In molti linguaggi di programmazione, e in R, i numeri interi non troppo grandi vengono registrati non in notazione scientifica, ma per esteso, in modo esatto (ciò è possibile in quanto per questi numeri la memoria del programma non viene usata per memorizzare l'esponente della notazione scientifica). Facciamo un esempio. Se in R batto:
123456*123456   ottengo:
  15241383936;  se poi batto:
123456*123456-15241383936   ottengo:
  0.   Invece se batto:
1.23456*1.23456-1.5241383936   ottengo:
  2.220446e-16.

    Un problema di calcolo si dice mal condizionato quando variando di poco i dati i risultati possono variare di molto. Ciò può dar luogo a valutazioni numeriche "sballate" non controllabili facilmente. Nei problemi più semplici questo in genere non accade, ma è bene tener presente questa possibilità. Come esempio facciamo solo quello della funzione polinomiale di grado 20 F seguente: x → (x−1)·(x−2)·…(x−20).  F(1) vale 0 ma F(1.0000001) vale −12164505732  e F(1.000005) vale −608214713003  (questi sono i valori che si ottengono con R;  con WolframAlpha devi battere, per F(1.0000001),  x=1+10^-7; Product[(x-k),{k, 1, 20}]).  Si intuisce come in un calcolo che coinvolga F non sia semplice tener conto degli errori di approssimazione.

    I fogli di calcolo operano sui numeri in modo simile. Con opportune opzioni si può associare ad ogni cella un particolare tipo di visualizzazione (forma esponenziale o no, numero di cifre visualizzate, …). Ma, purtroppo, le versioni in italiano hanno una modalità di rappresentazione e visualizzazione dei numeri non compatibile con quelle degli altri paesi.

    Consideriamo un foglio di calcolo elettronico così impostato:

 AB
1=2000 .07-2000 .03=(A1-0.04)*10^50
2=2000.07-2000.03=IF(A1=0.04;1;0)

["=IF(test;P;Q)" si comporta come "IF test THEN PRINT P ELSE PRINT Q"; in alcuni fogli di calcolo, a seconda delle opzioni scelte, viene usato "SE" al posto di "IF"]

    Se visualizziamo i valori, dando alla cella A2 l'opzione di rappresentare i numeri con 20 cifre, otteniamo:

 AB
1 0.04  -3.64E36 
2 0.03999999999996360000  0

o qualcosa di simile, con quasi tutti i tipi di foglio di calcolo.
    Anche per i fogli di calcolo 2000.07 - 2000.03 e 0.04 non sono uguali, anche se in apparenza, con i formati standard delle celle, il primo valore viene visualizzato sullo schermo come uguale al secondo. Comportamenti un po' "subdoli" come questo sono stati a volte fonte di errori nella gestione della contabilità mediante fogli elettronici da parte di alcune aziende.

    Si possono mettere a punto particolari programmi per visulizzare più cifre dei risultati, analoghi a quello visto sopra per la divisione tra interi. Esistono varie applicazioni matematiche che consentono di ottenere una quantità finita qualunque di cifre dei risultati di un certo calcolo.  Ma, tranne i casi in cui i risultati possono essere espressi come frazioni o in altri modi particolari (come radici quadrate di una frazione, per esempio), non esiste il modo di rappresentare esattamente il risultato di una operazione.  Comunque nei casi "pratici", in cui si opera con numeri approssimati, ciò non costituisce in genere un problema.

Esempio: qui
Esercizi:

 altri collegamenti     [nuova pagina]     Considerazioni Didattiche