03. Computer e calcolo numerico

I computer moderni implementato le operazioni aritmetiche e logiche direttamente nella CPU, per la precisione nella ALU (Unità Aritmetico Logica).

E' anche noto che la rappresentazione interna dei dati è sempre fatta in binario.

La capacità di operare con numeri espressi in una differente base di rappresentazione è introdotta dai linguaggi di programmazione, che si preoccupano di operare le opportune conversioni di base.

Quando effettuiamo delle operazioni aritmetiche coinvolgendo numeri interi, i risultati mostrati dal computer sono esattamente quelli attesi.

Cosa diversa accade quando si opera con numeri decimali (cioè numeri con la virgola) perché la conversione di base comporta risultati non facilmente prevedibili.

Partiamo osservando qual è la rappresentazione decimale di alcuni numeri razionali:

    • 1/3 in base dieci è un numero periodico che corrisponde a 0,3333...

    • 1/10 in base dieci corrisponde a 0,1

Operando in decimale con numeri periodici si introducono inevitabilmente degli errori di approssimazione.

Per non introdurre queste approssimazione è necessario operare sui numeri espressi come frazione. Ad esempio la somma 1/3+1/3+1/3 = 3/3 = 1 non introduce errori di approssimazione.

La rappresentazione dei numeri razionali in base binaria ha analoghe problematiche, ma un numero che in base dieci risulta essere periodico, potrebbe non esserlo in binario e viceversa.

Ad esempio:

    • 1/10 (base dieci) in binario è un numero periodico.

Un ulteriore problema da considerare è che la CPU utilizza un prefissato numero di bit per rappresentare i numeri, siano essi interi o in virgola mobile.

Quando si opera con i numeri interi le differenze non sono particolarmente evidenti, fatto salvo che esiste un limite superiore e inferiore per i numeri che si possono trattare.

Quando si opera con i numeri razionali, le differenze diventano significative: nella matematica tradizionale i numeri razionali costituiscono un insieme denso, e di conseguenza tra due numeri è sempre possibile individuarne infiniti altri.

Nell'aritmetica dei computer questo non è vero e, addirittura, potrebbe non essercene nessuno!

Questo non solo comporta approssimazione nei calcoli ma invalida anche le proprietà associativa e distributiva delle operazioni elementari. Ciò significa che, ad esempio, (a+b)*c potrebbe non corrispondere a a*c+b*c.

Naturalmente, in modo programmativo, è possibile estendere la capacità di calcolo della CPU e introdurre la capacità per un programma di operare su numeri con molte più cifre di quelle ammesse nativamente dalla CPU.

Inoltre, il programma potrebbe effettuare i calcoli "ragionando" direttamente su numeri rappresentati in base dieci, evitando le approssimazioni derivanti dalla conversione di base dei numeri in virgola mobile.

Il software bancario, ad esempio, utilizza tali accorgimenti.

Nella pratica occorre tenere presente che nativamente l'aritmetica dei computer ha i seguenti limiti:

Numeri interi

    • Singola precisione (32 bit):

      • con segno: da −2.147.483.648 a +2.147.483.647

      • senza segno: da 0 a +4.294.967.295

    • Doppia precisione (64 bit):

      • con segno: da −9.223.372.036.854.775.808 a +9.223.372.036.854.775.807

      • senza segno: da 0 a +18.446.744.073.709.551.615

Virgola mobile

    • Singola precisione (32 bit):

      • da 3.4*10-38 a 3.4*1038 (7 cifre significative)

    • Doppia precisione (64 bit):

      • da 1.7*10-308 a 1.7*10308 (15 cifre significative)


Esempi

Numero periodico:

#include <stdio.h>

int main()

{

float x;

int i;

x=0;

for (i=0;i<100000; i++)

x=x+0.5F;

printf("0.5 * 100000 = %f\n", x);

x=0;

for (i=0;i<100000; i++)

x=x+0.1F;

printf("0.1 * 100000 = %f\n", x);

return 0;

}

Il primo ciclo coinvolge il numero in virgola mobile 0,5 la cui rappresentazione binaria è precisa.

Anche il risultato finale è quello atteso.

Il secondo ciclo, invece, coinvolge il numero 0,1 che, in binario, risulta essere un numero periodico.

Il risultato finale è significativamente impreciso.

Proprietà associativa

#include <stdio.h>

int main()

{

float x,y,z,xPy, yPz, T;

x=16777216;

y=1;

z=-1;

xPy = x+y;

T = xPy+z;

printf("(x+y)+z = %f\n", T);

yPz = y+z;

T = x+yPz;

printf("x+(y+z) = %f\n", T);

return 0;

}

La somma 16777216 + 1 (in formato float) introduce un'approssimazione. La successiva istruzione opera quindi su un valore approssimato e di conseguenza anche il risultato finale è approssimato.

Effettuando le somme in un diverso ordine l'approssimazione non si introduce e il risultato finale è quello atteso.

Proprietà distributiva

Si consideri il seguente programma in C

#include <stdio.h>

int main()

{

float x, y, z, k, xz, yz, xPy;

x=0.1000081F;

y=0.1000082F;

z=10;

printf("x=%.7f\n", x);

printf("y=%.7f\n", y);

printf("z=%.7f\n", z);

xz = x*z;

yz = y*z;

k = xz+yz;

printf("(x*z)+(y*z) = %.7f\n", k);

xPy = x+y;

k=xPy * z;

printf("(x+y)*z = %.7f\n", k);

return 0;

}

L'esito è mostrato in figura:

Nota:

Il programma è stato scritto in maniera un po' strana, ed è naturale chiedersi come mai non siano state utilizzate le formule standard:

k=(x*z)+(y*z) e k = (x+y)*z

In effetti utilizzando queste formule il risultato dipende dal compilatore che si sta utilizzando e da quanto esso sia ottimizzato ed è possibile che le operazioni, pur corrette da un punto di vista matematico, non siano eseguite nell'ordine imposto dalle parentesi.

Il paradosso dell'incremento

#include <stdio.h>

int main()

{

float x;

int i;

x=16777216;

printf("x iniziale: %f\n", x);

for (i=0; i<100000;i++)

x+=1;

printf("Dopo 100000 incrementi di 1...\n");

printf("x finale: %f\n\n", x);

x=16777216;

x+=100000;

printf("Sommando 100000 ...\n");

printf("x finale: %f\n\n", x);

return 0;

}

Il comportamento del programma si spiega in questo modo:

Per incrementare la variabile x il sistema deve necessariamente effettuare un'approssimazione e la migliore approssimazione che il sistema può proporre consiste nel mantenere il valore già memorizzato.

Quindi, incrementi successivi non producono alcun effetto.

Cosa diversa, invece, si ottiene quando si somma il valore 100000...

in verità, se invece di 100000 si fosse sommato 100001 il risultato sarebbe stato lo stesso!