Home‎ > ‎Articles and Publications‎ > ‎Articles (ITA)‎ > ‎

Rappresentazione numerica e codifica dell'informazione

di A. Calderone


Breve introduzione ai principali sistemi di numerazione e alla codifica delle informazioni usati dai computer.


Siamo abituati a rappresentare i numeri come una sequenza di simboli o cifre. Ogni cifra ha un «peso» diverso a seconda della posizione che occupa nel numero. Ad esempio, il numero 123 è diverso dal numero 321 anche se le cifre usate per rappresentare entrambi i numeri sono uguali. Esiste un modo equivalente di scrivere i due numeri, cioè:

1 x 102 x 103 x 100   e   3 x 102 x 101 x 100

La seconda notazione mette in evidenza il peso effettivo di ciascuna cifra ed è pari a 10n, con 10 definito come base mentre n rappresenta la posizione della cifra nel numero, contata da destra verso sinistra, a partire da 0.
La notazione posizionale a cui siamo abituati nasconde le espressioni di sopra che sono implicitamente applicate.
Sappiamo che questo sistema di numerazione ha il nome di sistema decimale o a base dieci, proprio perché ogni numero può essere rappresentato combinando assieme i dieci simboli 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. 

Il sistema binario

In informatica, l’adozione del sistema binario per la rappresentazione dei numeri deriva dell’intima natura dei meccanismi che consento a un computer, più o meno complesso, di eseguire calcoli. 
Il sistema binario usa solo due cifre per rappresentare i numeri e cioè 0 e 1. 
La sequenza di queste cifre consente di rappresentare qualunque numero rappresentabile con altri sistemi di numerazione. Quindi è possibile convertire un numero fatto di cifre binarie o bit (binary digit) in un numero di un altro sistema di numerazione. 
Per esempio, proviamo a convertire il numero binario 1110 nella sua corrispondente rappresentazione decimale. Si osservi che ogni numero non decimale si legge scandendo ciascuna cifra per volta, e quindi il numero 1110 non si legge «millecentodieci» ma «uno, uno, uno, zero».
Procedendo come nel primo esempio assegniamo ad ogni posto occupato da ciascuna cifra un peso, che per il sistema binario, a base 2, vale 2n; n è la posizione di ogni bit a partire da destra verso sinistra. Quindi il numero binario 

1110 = 1 x 2+ 1 x 22 + 1 x 21 + 0 x 20 = 8+4+2=14. 

Sempre a motivo di esempio, convertiamo il numero binario 101011 con la medesima formula e quindi otteniamo:

1 x 250 x 21 x 23 0 x 21 x 21 1 x 20= 32+8+2+1 = 43 in base dieci. 

La conversione inversa può essere fatta nel modo che stiamo per descrivere.
Procedendo con l'esempio di sopra, prendiamo il nostro 43 in base dieci e lo dividiamo per 2 tenendo conto del resto, il risultato della divisione lo dividiamo ancora per due e così via fino a quando non avremo ottenuto 0 con resto di 1, cioè:  
 
43 / 2 =21 con resto 1 
21 / 2 =10 con resto 1 
10 / 2 = 5 con resto 0 
5 / 2 = 2 con resto 1 
2 / 2 = 1 con resto 0 
1 / 2 = 0 con resto 

Infine, leggendo il resto di ciascuna divisione dal basso verso l’alto otteniamo il numero di partenza 101011 in base 2.

Nibble, Byte, Word ...

La rappresentazione interna ai computer di un numero, si è detto, avviene adottando il sistema binario. Questi numeri binari vengono gestiti in formati ben precisi ovvero impaccati in una sequenza di bit dimensione fissata.
Questi pacchetti di bit sono raggruppati e nominati generalmente nel seguente modo:
  • 1 nibble = 4 bit
  • 1 byte = 2 nibble = 8 bit
  • 1 word = 2 byte = 4 nibble = 16 bit
  • 1 double word = 2 word = 4 byte = 8 nibble = 32 bit.
Si noti che il numero di bit di ogni formato è sempre proporzionale a 4. 
Tra i formati elencati il più usato è probabilmente il byte. 
Il termine «impaccato» deriva dal fatto che questi numeri sono sempre considerati come pacchetti di dimensione fissa e si rappresentano generlamente indicando sempre tutte le cifre anche se le più significative sono a zero.
Per esempio, un numero rappresentabile con un byte è comunque e sempre costituito da 8 bit. Il numero 0 in base 2, impaccato in un byte viene riscritto con una sequenza di 8 bit posti a 0 e cioè 0000 0000.
Il numero binario 101011 dell’esempio visto in precedenza viene impaccato in un byte come 0010 1011. Mentre adottando il formato word il numero diventa 0000 0000 0010 1011. Non ha invece senso impaccarlo in un nibble.

Usando un byte per rappresentare un sottoinsieme dei numeri naturali, ne possiamo rappresentare solo 256 (28), cioè dal numero 0000 0000 (0) al numero 1111 1111 (255): essendo un byte costituito da 8 bit, il numero più grande ottenibile combinando le due cifre 0 e 1 è 28-1=255. 

Usando una word si possono rappresentare invece 65.536 (216) numeri e 4.294.967.296 (232) usando una double word. Appena 16 (24) con un nibble. Più in generale, qualunque pacchetto di n bit si abbia, il numero di possibili combinazioni ottenibile con lo stesso è 2n.


MSB e LSB

Altri due termini molto ricorrenti nella terminologia informatica sono MSB e LSB. 
MSB sta per More Significant Bit o bit più significativo ed è il termine con cui spesso viene indicato il primo bit di un numero rappresentato in uno dei formati sopra elencati; analogamente l’ultimo bit viene chiamato LSB ovvero Less Significant Bit o bit meno significativo. 
Ad esempio, nel numero 00101011, MSB vale 0 e LSB vale 1.


Il sistema esadecimale

Benché i computer siano completamente a loro agio trattando i numeri in formato binario, noi lo troviamo meno naturale che trattare i numeri in formato decimale.
La traduzione tra i due sistemi non è immediata e una delle ragioni è che il sistema decimale e quello binario non mantengono costante il rapporto tra numero di cifre usate per rappresentare il medesimo numero.
Basti osservare la seguente tabella che mostra la corrispondenza dei numeri da 0 a 15 in binario e decimale:

0000010008
0001110019
00102101010
00113101111
01004110012
01015110113
01106111014
01117111115

Ci chiediamo quindi se esista (almeno) un sistema di numerazione che garantisca una corrispondenza biunivoca tra un certo numero di bit e una propria cifra. 
Nella tabella di sopra, da 0 a 9 i 4 bit corrispondono a una sola cifra decimale, da 10 a 15 a due distinte cifre.
Proviamo allora a riscrivere la precedente tabella nel seguente modo, rimpiazzando i numeri da 10 a 15 con le prime sei lettere dell'alfabeto, otteniamo la seguente tabella di conversione:

0000010008
0001110019
001021010A
001131011B
010041100C
010151101D
011061110E
011171111F

Questo artificio altro non è che l'adozione di un sistema di numerazione a base 16, conosciuto come sistema esadecimale. 
Il sistema esadecimale è estremamente comodo per rappresentare numeri binari in modo compatto, data la corrispondenza biunivoca tra numero di digit binari e numero di digit esadecimali.
Per esempio, per convertire il numero binario 0010 1011 e in esadecimale basta suddividerlo in nibble e tradurre questi nibble in cifre esadecimali ricordando la corrispondenza della tabella di sopra.
Quindi il numero binario 0010 1011 diventa 2Bh. Il postfiss h dopo il numero 2B indica che si tratta di un numero in notazione esadecimale. 
La conversione da esadecimale a decimale di 2Bh altro non è che  2 x 16+ B x 160 = 32 + 11 = 43.
Per la conversione si è tenuto conto che le lettere da A a F equivalgono ai numeri decimali da 10 a 15 e ciscuna cifra esadecimale ha peso 16n dove n è la posizione della cifra nel numero da destra a sinistra partendo da 0 (analogamente a quanto visto per la conversione del sistema binario).
La conversione inversa può essere fatta usando l'alforitmo del resto della divisione, quindi per esempio la conversione inversa da 43 a 2Bh si calcola come segue:

43 / 16 = 2 con resto 11 = 0Bh 
02 / 16 = 0 con resto 02 = 02h

Infine, si leggono le cifre del resto di ogni operazione dal basso verso l’alto e si ottiene 2Bh.

Il sistema esadecimale non è il solo a garantire una corrispondenza biunivoca tra cifre e bit. È possibile adottare altri sistemi di numerazione. Uno di questi potrebbe essere il sistema ottale (a base 8). 
Il vantaggio del sistema decimale sull'ottale è che il primo utilizza multipli di 4 bit contro i 3 del sistema ottale, e questo si sposa perfettamente con i formati impaccati visti in precedenza.

Complemento a 2 e numeri negativi

Un'altra caratteristica dei computer è che sono molto abili nel fare calcoli con numeri interi. I numeri reali, come vedremo possono essere approssimati e trattati con numeri in virgola fissa o con quelli che sono definiti numeri in virgola mobile o floating point. In entrambi i casi si tratta di approssimazioni realizzate in realtà mediante calcoli su numeri interi.
Fatta questa considerazione, proviamo a spiegare un ingegnoso metodo per la rappresentazione di numeri negativi.
Il metodo in oggetto è conosciuto come complemento a 2. Complementare un numero binario significa invertire tutti i bit dello stesso (complemento a 1); nel complemento a due, oltre la normale inversione dei bit, viene anche sommato 1 al numero complementato.

Esempi:
  • il complemento a 1 del byte 00101011b (2Bh) è il numero 11010100b (D4h); 
  • il complemento a 2 del byte 2Bh, si ottiene aggiungendo 1 al complemento a 1 dello stesso, e cioè:
00101011 (2Bh) 
11010100+(2Bh complementato a 1) 
00000001= 
--------- 
11010101 (2Bh complementato a 2)

Il numero 2Bh (43) complementato a 2 può essere utilizzato per rappresentare il numero negativo -2Bh (-43).
Per dimostrare che questa cosa abbia senso, si può provare a sommare tra loro i due numeri 2B e D4. 
Il risultato della somma dovrà quindi essere 0.
Proviamo, dunque, a sommare 2Bh e il suo complemento a 2, in base 2: 

. 00101011+ (2Bh) 
. 11010101= (D4h cioè 2Bh complementato a 2) 
. --------- 
1 00000000

Se trascuriamo il bit più significativo del risultato, i restanti 8 bit sono posti tutti a 0. 
In realtà siamo "costretti" a trascurare il MSB del risultato, perché non c’è spazio per questo bit dentro il nostro byte. 
Il bit "trascurato" è chiamato bit di riporto o Carry. Se non si tiene conto del Carry nel risultato, la somma vale zero. In questo modo -2Bh può essere ottenuto complementando a due 2Bh, così come ogni altro numero negativo può essere ottenuto complementando a 2 il numero positivo equivalente in modulo. 
Osserviamo che, l'MSB di un byte usato per rappresentare i numeri negativi, è sempre 1.

Facciamo ancora un esempio. Proviamo, sempre in binario a fare la differenza tra 34h (00110100b) e il numero 24h (00100100b), il risultato dovrebbe essere 10h (00010000b). 
Per quanto visto, fare la differenza tra 34h e 24h è analogo a fare la somma tra 34h e il complemento a 2 di 24h. Procediamo dunque, complementando a 2 il numero 24h poi sommiamolo a 34h: 
.

0010 0100 (24h) 
1101 1011+(24h complementato a 1 = DBh) 
0000 0001= 
--------- 
1101 1100 (24h complementato a 2 = DCh)

Sommiamo ora 24h complementato a 2 (DCh) a 34h.

. 1101 1100+ (DCh) 
. 0011 0100= (34h) 
----------- 
0001 0000 (10h

Anche in questo caso se non teniamo conto del bit di Carry, il risultato contenuto negli 8 bit meno significativi è quello atteso.


Formato in virgola mobile

Il formato numerico in virgola mobile o floating point è anche conosciuto come notazione scientifica. Per esempio, il numero -1234000 può essere riscritto nella notazione scientifica come -1,234x106. Il numero così rappresentato può essere scomposto nelle tre parti: segno -, mantissa 1,234 ed esponente 6. Naturalmente il numero -1234000 può essere riscritto, sempre in notazione scientifica, in infiniti altri modi, tuttavia è necessario scegliere una fra le possibili rappresentazioni. Ad esempio, -0,1234x107 sfrutta la particolarità di poter rappresentare come mantissa le cifre che stanno appena dopo la virgola, essendo la prima cifra implicitamente sempre lo zero. 
Delle possibili rappresentazioni, ne possiamo adottare una che chiamiamo normalizzata; analogamente le altre possibili rappresentazioni sono chiamate denormalizzate. 
La normalizzazione serve per attribuire una dimensione ben precisa ai tre campi del numero floating point, affinché questo possa essere rappresentato in un formato opportunamente impaccato.
Delle possibili normalizzazioni e formati per un numero in floating point, abbiamo scelto di esaminare lo standard IEEE 754 perchè largamente adottato.
Un numero floating point di questo standard è suddiviso nei tre campi già visti, e precisamente: segno, mantissa ed esponente. L’ordine in cui i campi sono rappresentati è strettamente legato alla convenzione stabilente che il campo più significativo va posto in posizione più "privilegiata" rispetto a quello meno significativo. Il bit di segno è sicuramente il campo più importante, segue poi l’esponente ed infine la mantissa. Il razionale è nella seguente considerazione: mentre una variazione di un bit della mantissa modifica il numero ma non il suo ordine di grandezza, la variazione di un bit dell’esponente cambia l’ordine di grandezza del numero stesso, e addirittura la variazione del bit di segno stravolge il suo valore relativo. 
Analizziamo ora in dettaglio i singoli campi di un numero in virgola mobile.

Il campo segno.

Il campo segno è costituito da un singolo bit, che assume valore 0 per indicare che il numero floating point è positivo, e 1 per indicare che il numero è negativo.
Se il numero floating point è zero, e ciò avviene quando nei campi mantissa ed esponente tutti i bit sono posti a 0, il campo segno può assumere indifferentemente, sia valore negativo che positivo. In sostanza, benché esista uno zero positivo e uno zero negativo (+0 e -0) in realtà i due numeri rappresentano la stessa quantità e cioè zero.

Il campo mantissa

La mantissa dei numeri floating point è della forma X1, X2X3X4...Xn, dove X(i=1,2,3,..,n) è una cifra binaria. Per ottenere la migliore precisione, cioè massimizzando il numero di bit significativi della mantissa, è necessario normalizzare il numero floating point, come visto nella precedente notazione scientifica. Occorre, cioè regolare l’esponente in modo da avere X1 = 0, e Xi diverso da 0 per i > 1evitando così, di sprecare preziosi bit riempiendo la mantissa di zeri all’inizio del numero stesso. 
Grazie alla rappresentazione in binario e all’adozione della potenza di 2, è possibile, sempre agendo sull’esponente, porre il primo bit della mantissa X1 = 1, invece che zero, ottenendo una maggiore precisione, a parità di bit utilizzati. Per esempio, il numero binario 0,0001101111..., diminuendo l’esponente di 4, si può rappresentare come 1,101111... anziché come 0,1101111... guadagnando così un preziosissimo bit nel campo mantissa. 
Normalizzando in questo modo, ogni numero floating point, si può omettere il primo bit della mantissa che viene assunto implicitamente uguale a 1, cosicché la mantissa può essere riscritta nella forma 1,X2X3X4...Xndove solo gli Xi occuperanno un posto nel campo mantissa.
Esistono comunque delle eccezioni a questa regola, e una in particolare è la rappresentazione di 0, che viene fatta, come già detto, ponendo a zero i bit del campo mantissa e quelli del campo esponente.

Il campo esponente

Il campo esponente specifica la potenza di 2 che moltiplicata alla mantissa, determina il valore in modulo del numero in virgola mobile. Alcuni elaboratori usavano potenze di 16 anziché di 2 (come l’IBM 360/370). Il vantaggio di usare potenze di 2 risiede soprattutto nel fatto che a parità di complessità hardware i calcolatori che adottano le potenze di due sono più veloci di quelli che adottano potenze di 16. Inoltre, usando potenze di due si può usare il "trucchetto" di porre a 1 il MSB della mantissa, piuttosto che a zero. 

Per rendere possibili esponenti negativi, il campo esponente contiene la somma dell’effettivo esponente e di una costante positiva detta bias (polarizzazione) che viene assunta come esponente neutro (0). 

La bias vale 127 per la singola precisione, 1023 per la doppia e 16383 per la precisione estesa. Per esempio, se l’esponente di un numero in singola precisione fosse 5, il valore memorizzato nel campo esponente sarebbe 127+5=132; se l’esponente fosse -5 il campo esponente conterrebbe allora 127-5=122.

La seguente tabella mostra la rappresentazione interna dei diveris formati dei numeri in floating point (standard IEEE 754).

Singola precisione 32 bits (bit di segno)8 bit - ESPONENTE23 bit - MANTISSA
Doppia Precisione 64 bits (bit di segno)11 bit - ESPONENTE52 bit - MANTISSA
Precisione Estesa 80 bits (bit di segno)23 bit - ESPONENTE64 bit - MANTISSA


Espressione di conversione.

La conversione di un numero floating point, nel formato sopra esaminato può essere effettuata tendendo in considerazione le regole già stabilite per rappresentazione dei vari campi. Volendo formalizzare le stesse, potremmo usare le seguenti espressioni, per la conversione dei numeri in decimale:
  1. (-1)segno (1,X1X2...X23) 2(esponente-127) per la singola precisione;
  2. (-1)segno (1,X1X2...X52) 2(esponente-1023) per la doppia precisione;
  3. (-1)segno (1,X1X2...X64) 2(esponente-16383) per la precisione estesa.
Esempio. Convertiamo il numero in singola precisione rappresentato come

1 01111110 11000000000000000000000

dove segno=1, esponente=126, X=1 e X2=1 (mentre non influiscono gli X con indice > 2). Perciò usando la prima formula si ha

(-1)1 (1,11b) 2(126-127) = -1 (1,75d) 2-1 = -1,75. 

Si osservi, che le cifre binarie che stanno dopo la virgola hanno peso 2-nDove n è la posizione dei bit contata da sinistra verso destra. Dunque i primi due bit della mantissa corrispondono al numero decimale 0,75 ottenuto come

2-1 + 2-2 =1/2+1/4=0,75. 

Sommando poi il MSB (che non compare nel numero ma che sappiamo essere 1) si ottiene 1,75 in base dieci equivalente al numero 1,11 in base 2.


Codifica BCD

Tutte le operazioni aritmetiche discusse finora riguardavano numeri binari. Questo perché i computer pensano in binario. Ma l’uomo no: il nostro mondo è decimale. Perché allora i calcolatori devono essere tanto stupidi da continuare a "pensare" in binario? Il fatto che funzionino con due soli livelli di tensione non significa che essi debbano rappresentare i loro numeri in notazione binaria. Certamente la sequenza dei bit 0 e 1 potrebbe essere usata per codificare separatamente ogni cifra decimale di un numero. Per esempio, invece di rappresentare il numero decimale 43 con il suo equivalente binario 00101011, potrebbe essere rappresentato dalla codifica decimale di 4 (0100b), seguita dalla codifica decimale di 3 (0011b), ottenendo la rappresentazione 0100 0011b. Si noti che questa è una codifica binaria delle cifre decimali, e viene appropriatamente chiamata Binary-Coded Decimal o BCD. 
La seguente tabella mostra la codifica BCD di ciascuna cifra decimale:

0000
0
1000
8
0001
1
1001
9
0010
2
1010
*
0011
3
1011
*
0100
4
1100
*
0101
5
1101
*
0110
6
1110
*
0111
7
1111
*
Parole di codice non valide

Una delle ragioni per cui i computer usano internamente notazione binaria rispetto quella BCD, è che questa è più compatta. Per esempio, il numero 125 può essere rappresentato con 8 bit in notazione binaria compatta (0111 1101b) mentre richiede 12 bit in formato BCD (0001 0010 0101b). 
Tuttavia, accade spesso, di dover ricorrere all’uso del codice BCD per la rappresentazione dei numeri decimali affinché possano essere agevolmente manipolati e gestiti. Questo implica che la maggior parte dei processori siano dotati di un set di istruzioni che gli consenta di operare sui numeri BCD, oltre che sui numeri binari; o che comunque, abbiano istruzioni che gli consentano di effettuare una rapida conversione dall’una all’altra codifica.


Codifica delle informazioni

Ogni compter o dispositivo come stampante, modem, etc, utilizza differenti componenti interni e differenti codifiche per le informazioni, quindi è importante, che quando più dispositivi sono collegati, la comunicazione fra essi avvenga senza ambiguità. É quindi necessario attribuire, ad ogni valore che i dati possono assumere, una configurazione opportuna di bit secondo regole stabilite. Queste regole sono i codici.

In generale si può affermare che, per la trasmissione delle informazioni verso il mondo esterno, i dispositivi possono utilizzare codici diversi rispetto a quelli usati per l'elaborazione dei dati al proprio interno. Esistono infatti, periferiche come le stampanti che possono essere collegate indifferentemente ad elaboratori completamente diversi, che quindi al proprio interno, lavorano in maniera del tutto differente. 

La codifica delle informazioni garantisce la possibilità che gli stessi computer, diversi per funzionamento interno, possano comunicare tra loro. Questo non potrebbe avvenire se non parlassero tutti la «stessa lingua» ovvero lo stesso codice.

I criteri con cui vengono attribuiti i bit per generare i codici sono svariati: abbiamo visto, ad esempio, la codifica BCD di un numero, dove le dieci cifre decimali corrispondevano alla combinazione di 4 bit. Tuttavia questa codifica procurava il fastidio di dover scartare ben 6 combinazioni dei 4 bit, perché eccedevano rispetto il numero delle cifre decimali richieste. Va ricordato, a tal proposito, che per codificare M simboli occorrono 2n combinazioni binarie tali che sia 2n-1 < M < 2n quindi se M=10 si ha 2< 10 < 24occorrono allora almeno 4 bit per rappresentare 10 cifre, e ciò significa che di 16 (24) possibili combinazioni solo 10 sono utilizzate, le rimanenti 6 vengono scartate. Le 6 combinazioni scartate sono dette parole di codice non valide.

Il codice ASCII

Per la comunicazione tra il microprocessore e le periferiche (si pensi, ad esempio, alla gestione di una tastiera o di una stampante) è necessario trasmettere non solo cifre decimali, ma anche caratteri speciali; in genere la comunicazione include:
  • lettere maiuscole e minuscole dell'alfabeto;
  • cifre decimali;
  • caratteri di punteggiatura ( " ; , : . ! ? ecc... );
  • caratteri aritmetici ( + * - / ecc... );
  • simboli grafici;
  • caratteri di controllo.

In totale almeno un centinaio di simboli.
Il numero minimo di bit richiesto è 7 bit essendo 64=2< 128 <= 27. 

Il codice ASCII (American Standard Code for Information Interchange ) è certamente il più utilizzato anche se oggigiorno lo si tende a rimpiazzare con UNICODE, per l'indipenza di questo dalle lingue e dalle piattaforme utilizzate.

Il codice ASCII, nella sua versione universalmente riconosciuta, è composto da 128 simboli. Tuttavia molti costruttori di computer lo hanno esteso usando 8 bit anziché 7, aggiungendo quindi altri 128 caratteri particolari, pur mantenendo invariati i primi 128 caratteri originali del codice.
I primi 32 caratteri esadecimali da 00 a 1Fh, sono caratteri di controllo; i più rilevanti sono:
  • Nul = 00h.
  • ACK = segnale di riconoscimento che il dispositivo di ricezione invia a quello che trasmette, 06h.
  • BEL = segnale acustico, campanello, 07h.
  • CR = ritorno di carrello, 0Dh.
  • LF = avanzamento del carrello di una riga, 0Ah.
  • SYN = sincronismo tra sistemi di trasmissione, 16h.
  • EOT = fine della trasmissione, 04h.
  • STX = inizio di un messaggio, 02h
  • ETX = fine di un messaggio, 03h
  • SP = spaziatura, 20h
Comments