Da un punto di vista concreto i numeri vengono rappresentati mediante la specificazione del nome della variabile in cui vogliamo immagazzinare il numero e del tipo di numero in questione. L’allocazione del numero può essere vista come una sequenza di bit. Come viene immagazzinato il numero in questa sequenza? Per capirlo prendiamo in esame in numero intero scelto completamente a caso: 5207. Nella notazione decimale associamo ad ogni posizione assunta dalle cifre di questo numero una diversa potenza di 10 (10^0 per la posizione relativa alle unità, 10^1 per quella relativa alle decine e così via). Ovviamente, moltiplicando ogni cifra per la potenza di 10 associata alla sua posizione nel numero e sommando i valori così ottenuto avremo il numero iniziale
7*10^0+0*10^1+2*10^2+5*10^3=7*1+0*10+2*100+5*1000=7+0+200+5000=5207
Il problema che affrontiamo adesso sarà quello di applicare il ragionamento appena descritto per quanto riguarda i numeri allocati in una sequenza di bit. Per questi numeri infatti non verrà applicata una notazione decimale poiché i dati in memoria possono assumere solo due cifre (0 e 1). Tuttavia il procedimento che ne seguirà risulta molto simile a quello precedente. Consideriamo anche in questo caso un numero scelto a caso (ma questa volta composto solo da sequenze di 1 e 0): 10111. Associamo adesso pesi diversi alle singole cifre in base alla posizione che ricoprono all’interno del numero. In questo caso i pesi non saranno più potenze di 10 ma di 2
1*2^0+1*2^1+1*2^2+0*2^3+1*2^4=1+2+4+0+16=23
Dettaglio tecnico: in questi bit dovrà essere rappresentato anche il segno del numero. In generale i bit che ho a disposizione dipendono dal tipo di variabile che ho scelto
- Per le variabili Int sono associate stringhe da 32 bit
- Per le variabili Long sono associate stringhe da 64 bit
Data un’allocazione di memoria, i numeri interi che posso rappresentare si troveranno sicuramente tra un valore minimo e un valore massimo, entrambi interi. Se provassi a studiare un numero che si trova al di fuori di questo intervallo commetterei un errore di overflow. Ovviamente, l’ampiezza dell’intervallo in cui un valore può essere definito dipende da suo tipo
- I tipi Int (16 bit) possono assumere valori che vanno da -2.147.483.648 a 2.147.483.648
- I tipi Long (32 bit) possono assumere valori che vanno da circa -9*10^18 a 9*10^18
Ovviamente possiamo delimitare un intervallo di accettazione anche per i valori di tipo float, che vedremo più avanti. In particolare
- I tipi Single (16 bit) possono assumere valori che vanno da circa -3.4*10^38 a 3.4*0^38
- I tipi Double (32 bit) possono assumere valori che vanno da circa -1.7*10^308 a 1.7*0^308
- I tipi Decimal (64 bit) possono assumere valori che vanno da circa -7.9*10^28 a 7.9*0^28 (N.B: le variabili associate a questo tipo sono le uniche che sfruttano un sistema decimale e non binario)
Per quanto riguarda le variabili Float consideriamo un numero scelto a caso ed espresso secondo il sistema decimale: 3727.215 Anche qui assocerò una potenza di 10 ad ogni cifra in base alla sua posizione. L’unica differenza rispetto all’analisi fatta precedentemente per i numeri interi è che in questo caso dovremo associare dei pesi anche ai numeri decimali. Non è difficile comprendere che questi nuovi pesi saranno rappresentati da potenze di 10 con esponenti negativi (10^-1 per le decine, 10^-2 per i centesimi e 10^-3 per i millesimi). Ovviamente questo processo può essere applicato anche per i numeri espressi nel sistema binario sostituendo le potenze in base 10 con quelle in base 2. Da questo inquadramento concettuale notiamo due concetti fondamentali:
- I numeri che posso rappresentare nella stringa sono compresi tra un minimo e un massimo
- Poiché sto rappresentando numeri con la virgola, il numero di valori rappresentabili deve essere necessariamente finito
Tuttavia noi sappiamo che tra due valori esistono infiniti numeri reali e ciò sembra essere in contraddizione con il secondo concetto. La risposta a questo problema è data dal fatto che i numeri che potremo rappresentare mediante il sistema binario saranno solo quelli ottenibili mediante le potenze in base due. A questo proposito consideriamo un numero espresso nel sistema binario: 11101,101 E’ possibile considerare questo numero come prodotto tra due numeri interi sfruttando le potenze in base due. Possiamo dunque scrivere la seguente relazione:
11101,101=11101101*2^3
dove l’esponente di 2 equivale a quante cifre sono presenti dopo la virgola. Il numero iniziale senza la virgola prende il nome di Mantissa. In sostanza, ho trasformato il problema della rappresentazione di un numero con la virgola nel problema della rappresentazione di due interi (Mantissa ed esponente) in una stringa di bit. Pertanto possiamo suddividere i bit presenti in una stringa in 3 sottogruppi:
- Quello relativo al segno del numero (sarà un solo bit)
- Quello relativo alla Mantissa
- Quello relativo all’esponente
e ciò è definito floating point. Consideriamo adesso i problemi che si possono verificare in merito all’approssimazione. Se introducessimo nella stringa un esponente troppo grande il numero in questione potrebbe trovarsi al di fuori dell’intervallo delimitato dal valore minimo e massimo (che a loro volta dipende dal numero di bit da cui è composta la nostra stringa). Tuttavia, esiste anche il problema relativo all’associazione dell’esponente con un numero troppo piccolo: in quel caso il nostro valore si avvicinerebbe a 0 ma, poiché i bit dell’esponente sono finiti potrò avvicinarmi al valore nullo solo fino ad un certo punto (questo è detto problema di underflow)