Capitolo 80.   Dai sistemi di numerazione all'organizzazione della memoria

80.1   Sistemi di numerazione

I sistemi di numerazione più comuni sono di tipo posizionale, definiti in tal modo perché la posizione in cui appaiono le cifre ha significato. I sistemi di numerazione posizionali si distinguono per la base di numerazione.

80.1.1   Sistema decimale

Il sistema di numerazione decimale è tale perché utilizza dieci simboli, pertanto è un sistema in base dieci. Trattandosi di un sistema di numerazione posizionale, le cifre numeriche, da «0» a «9», vanno considerate secondo la collocazione relativa tra di loro.

A titolo di esempio si può prendere il numero 745 che, eventualmente, va rappresentato in modo preciso come 74510: secondo l'esperienza comune si comprende che si tratta di settecento, più quaranta, più cinque, ovvero, settecentoquarantacinque. Si arriva a questo valore sapendo che la prima cifra a destra rappresenta delle unità (cinque unità), la seconda cifra a partire da destra rappresenta delle decine (quattro decine), la terza cifra a partire da destra rappresenta delle centinaia (sette centinaia).

Figura 80.1. Esempio di scomposizione di un numero in base dieci.

745 in base dieci

Figura 80.2. Scomposizione di un numero in base dieci.

745 in base dieci

80.1.2   Sistema binario

Il sistema di numerazione binario (in base due), utilizza due simboli: «0» e «1».

Figura 80.3. Esempio di scomposizione di un numero in base due.

10010_(2)

Figura 80.4. Scomposizione di un numero in base due.

10010_(2)

80.1.2.1   Esercizio

Si traduca il valore 111100112 in base dieci, con l'aiuto dello schema successivo, completandolo con una matita o con una penna, eventualmente con l'uso di una calcolatrice comune:

conversione da binario a decimale

Pertanto, il risultato in base dieci è:

risultato in base dieci

80.1.2.2   Esercizio

Si traduca il valore 011001102 in base dieci, con l'aiuto dello schema successivo, completandolo con una matita o con una penna, eventualmente con l'uso di una calcolatrice comune:

conversione da binario a decimale

Pertanto, il risultato in base dieci è:

risultato in base dieci

80.1.3   Sistema ottale

Il sistema di numerazione ottale (in base otto), utilizza otto simboli: da «0» a «7».

Figura 80.9. Esempio di scomposizione di un numero in base otto.

354_(8)

Figura 80.10. Scomposizione di un numero in base otto.

354_(8)

80.1.3.1   Esercizio

Si traduca il valore 13578 in base dieci, con l'aiuto dello schema successivo, completandolo con una matita o con una penna, eventualmente con l'uso di una calcolatrice comune:

conversione da ottale a decimale

Pertanto, il risultato in base dieci è:

risultato in base dieci

80.1.3.2   Esercizio

Si traduca il valore 75318 in base dieci, con l'aiuto dello schema successivo, completandolo con una matita o con una penna, eventualmente con l'uso di una calcolatrice comune:

conversione da ottale a decimale

Pertanto, il risultato in base dieci è:

risultato in base dieci

80.1.4   Sistema esadecimale

Il sistema di numerazione esadecimale (in base sedici), utilizza sedici simboli: le cifre numeriche da «0» a «9» e le lettere (maiuscole) dalla «A» alla «F».

Figura 80.15. Esempio di scomposizione di un numero in base sedici.

9C8_(16)

Figura 80.16. Scomposizione di un numero in base sedici.

9C8_(16)

80.1.4.1   Esercizio

Si traduca il valore 15AC16 in base dieci, con l'aiuto dello schema successivo, completandolo con una matita o con una penna, eventualmente con l'uso di una calcolatrice comune:

conversione da esadecimale a decimale

Pertanto, il risultato in base dieci è:

risultato in base dieci

80.1.4.2   Esercizio

Si traduca il valore CF5816 in base dieci, con l'aiuto dello schema successivo, completandolo con una matita o con una penna, eventualmente con l'uso di una calcolatrice comune:

conversione da esadecimale a decimale

Pertanto, il risultato in base dieci è:

risultato in base dieci

80.2   Conversioni numeriche di valori interi

Un numero intero espresso in base dieci, viene interpretato sommando il valore di ogni singola cifra moltiplicando per 10n (n rappresenta la cifra n-esima, a partire da zero). Per esempio, 12 345 si può esprimere come 5×100 + 4×101 + 3×102 + 2×103 + 1×104. Nello stesso modo, si può scomporre un numero per esprimerlo in base dieci dividendo ripetutamente il numero per la base, recuperando ogni volta il resto della divisione. Per esempio, il valore 12 345 (che ovviamente è già espresso in base dieci), si scompone nel modo seguente: 12 345/10=1 234 con il resto di cinque; 1 234/10=123 con il resto di quattro; 123/10=12 con il resto di tre; 12/10=1 con il resto di due; 1/10=0 con il resto di uno (quando si ottiene un quoziente nullo, la conversione è terminata). Ecco che la sequenza dei resti dà il numero espresso in base dieci: 12 345.

Riquadro 80.21. Il resto della divisione.

Per riuscire a convertire un numero intero da una base di numerazione a un'altra, occorre sapere calcolare il resto della divisione.

Si immagini di avere un sacchetto di nove palline uguali, da dividere equamente fra quattro amici. Per calcolare quante palline spettano a ognuno, si esegue la divisione seguente:

9/4 = 2,25

Il risultato intero della divisione è due, pertanto ognuno dei quattro amici può avere due palline e il resto della divisione è costituito dalle palline che non possono essere suddivise. Come si comprende facilmente, il resto è di una pallina:

9 - (2×4) = 1

80.2.1   Numerazione ottale

La numerazione ottale, ovvero in base otto, si avvale di otto cifre per rappresentare i valori: da zero a sette. La tecnica di conversione di un numero ottale in un numero decimale è la stessa mostrata a titolo esemplificativo per il sistema decimale, con la differenza che la base di numerazione è otto. Per esempio, per interpretare il numero ottale 123458, si procede come segue: 5×80 + 4×81 + 3×82 + 2×83 + 1×84. Pertanto, lo stesso numero si potrebbe rappresentare in base dieci come 5 349. Al contrario, per convertire il numero 5 349 (qui espresso in base 10), si può procedere nel modo seguente: 5 349/8=668 con il resto di cinque; 668/8=83 con il resto di quattro; 83/8=10 con il resto di tre; 10/8=1 con il resto di due; 1/8=0 con il resto di uno. Ecco che così si riottiene il numero ottale 123458.

Figura 80.22. Conversione in base otto.

da base otto a base dieci

Figura 80.23. Calcolo del valore corrispondente di un numero espresso in base otto.

da base otto a base dieci

80.2.1.1   Esercizio

Si traduca il valore 123410 in base otto, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

ottale

80.2.1.2   Esercizio

Si traduca il valore 432110 in base otto, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

ottale

80.2.2   Numerazione esadecimale

La numerazione esadecimale, ovvero in base sedici, funziona in modo analogo a quella ottale, con la differenza che si avvale di 16 cifre per rappresentare i valori, per cui si usano le cifre numeriche da zero a nove, più le lettere da «A» a «F» per i valori successivi. In pratica, la lettera «A» nelle unità corrisponde al numero 10 e la lettera «F» nelle unità corrisponde al numero 15.

La tecnica di conversione è la stessa già vista per il sistema ottale, tenendo conto della difficoltà ulteriore introdotta dalle lettere aggiuntive. Per esempio, per interpretare il numero esadecimale 19ADF16, si procede come segue: 15×160 + 13×161 + 10×162 + 9×163 + 1×164. Pertanto, lo stesso numero si potrebbe rappresentare in base dieci come 105 183. Al contrario, per convertire il numero 105 183 (qui espresso in base 10), si può procedere nel modo seguente: 105 183/16=6 573 con il resto di 15, ovvero F16; 6 573/16=410 con il resto di 13, ovvero D16; 410/16=25 con il resto di 10, ovvero A16; 25/16=1 con il resto di nove; 1/16=0 con il resto di uno. Ecco che così si riottiene il numero esadecimale 19ADF16.

Figura 80.26. Conversione in base sedici.

da base dieci a base sedici

Figura 80.27. Calcolo del valore corrispondente di un numero espresso in base sedici.

da base sedici a base dieci

80.2.2.1   Esercizio

Si traduca il valore 4422110 in base sedici, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

esadecimale

80.2.2.2   Esercizio

Si traduca il valore 1224410 in base sedici, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

esadecimale

80.2.3   Numerazione binaria

La numerazione binaria, ovvero in base due, si avvale di sole due cifre per rappresentare i valori: zero e uno. Si tratta evidentemente di un esempio limite di rappresentazione di valori, dal momento che utilizza il minor numero di cifre. Questo fatto semplifica in pratica la conversione.

Seguendo la logica degli esempi già mostrati, si analizza brevemente la conversione del numero binario 11002: 0×20 + 0×21 + 1×22 + 1×23. Pertanto, lo stesso numero si potrebbe rappresentare come 12 secondo il sistema standard. Al contrario, per convertire il numero 12, si può procedere nel modo seguente: 12/2=6 con il resto di zero; 6/2=3 con il resto di zero; 3/2=1 con il resto di uno; 1/2=0 con il resto di uno. Ecco che così si riottiene il numero binario 11002.

Figura 80.30. Conversione in base due.

da base dieci a base due

Figura 80.31. Calcolo del valore corrispondente di un numero espresso in base due.

da base due a base dieci

Si può convertire un numero in binario, in modo più semplice, se si costruisce una tabellina simile a quella seguente:

tabellina

I valori indicati sopra ogni casellina sono la sequenza delle potenze di due: 20, 21, 22,... 2n.

Se si vuole convertire un numero binario in base dieci, basta disporre le sue cifre dentro le caselline, allineato a destra, moltiplicando ogni singola cifra per il valore che gli appare sopra, sommando poi ciò che si ottiene. Per esempio:

tabellina

Per trovare il corrispondente binario di un numero in base 10, basta sottrarre sempre il valore più grande possibile. Supponendo di voler convertire il numero 123 in binario, si possono sottrarre i valori: 64, 32, 16, 8, 2 e 1:

tabellina

80.2.3.1   Esercizio

Si traduca il valore 123410 in base due, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

tabellina

80.2.3.2   Esercizio

Si traduca il valore 432110 in base due, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

tabellina

80.2.4   Conversione tra ottale, esadecimale e binario

I sistemi di numerazione ottale ed esadecimale hanno la proprietà di convertirsi in modo facile in binario e viceversa. Infatti, una cifra ottale richiede esattamente tre cifre binarie per la sua rappresentazione, mentre una cifra esadecimale richiede quattro cifre binarie per la sua rappresentazione. Per esempio, il numero ottale 1238 si converte facilmente in 0010100112; inoltre, il numero esadecimale 3C16 si converte facilmente in 001111002.

Figura 80.37. Conversione tra la numerazione ottale e numerazione binaria.

da base otto a base due e viceversa

In pratica, è sufficiente convertire ogni cifra ottale o esadecimale nel valore corrispondente in binario. Quindi, sempre nel caso di 1238, si ottengono 0012, 0102 e 0112, che basta attaccare come già è stato mostrato. Nello stesso modo si procede nel caso di 3C16, che forma rispettivamente 00112 e 11002.

Figura 80.38. Conversione tra la numerazione esadecimale e numerazione binaria.

da base sedici a base due e viceversa

È evidente che risulta facilitata ugualmente la conversione da binario a ottale o da binario a esadecimale.

Figura 80.39. Riassunto della conversione tra binario-ottale e binario-esadecimale.

riassunto della conversione tra binario-ottale e binario-esadecimale

80.2.4.1   Esercizio

Si traduca il valore ABC16 in base due e quindi in base otto, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

da base sedici a base otto

80.2.4.2   Esercizio

Si traduca il valore 76558 in base due e quindi in base sedici, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

da base otto a base sedici

80.3   Conversioni numeriche di valori non interi

La conversione di valori non interi in basi di numerazione differenti, richiede un procedimento più complesso, dove si convertono, separatamente, la parte intera e la parte restante.

Il procedimento di scomposizione di un numero che contenga delle cifre dopo la parte intera, si svolge in modo simile a quello di un numero intero, con la differenza che le cifre dopo la parte intera vanno moltiplicate per la base elevata a una potenza negativa. Per esempio, il numero 12,34510 si può esprimere come 1×101 + 2×100 + 3×10-1 + 4×10-2 + 5×10-3.

80.3.1   Conversione da base 10 ad altre basi

Come accennato nella premessa del capitolo, la conversione di un numero in un'altra base procede in due fasi: una per la parte intera, l'altra per la parte restante, unendo poi i due valori trovati. Per comprendere il meccanismo conviene simulare una conversione dalla base 10 alla stessa base 10, con un esempio: 12,345.

Per la parte intera, si procede come al solito, dividendo per la base di numerazione del numero da trovare e raccogliendo i resti; per la parte rimanente, il procedimento richiede invece di moltiplicare il valore per la base di destinazione e raccogliere le cifre intere trovate. Si osservi la figura successiva che rappresenta il procedimento.

Figura 80.42. Conversione da base 10 a base 10.

da base dieci a base dieci

Quello che si deve osservare dalla figura è che l'ordine delle cifre cambia nelle due fasi del calcolo. Nelle figure successive si vedono altri esempi di conversione nelle altre basi di numerazione comuni.

Figura 80.43. Conversione da base 10 a base 16.

da base dieci a base sedici

Figura 80.44. Conversione da base 10 a base 8.

da base dieci a base otto

Figura 80.45. Conversione da base 10 a base 2.

da base dieci a base due

80.3.1.1   Esercizio

Si traduca il valore 43,2110 in base otto, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

ottale

80.3.1.2   Esercizio

Si traduca il valore 765,432110 in base sedici, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

esadecimale

80.3.1.3   Esercizio

Si traduca il valore 21,1110 in base due, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

binario

80.3.2   Conversione a base 10 da altre basi

Per convertire un numero da una base di numerazione qualunque alla base 10, è necessario attribuire a ogni cifra il valore corrispondente, da sommare poi per ottenere il valore complessivo. Nelle figure successive si vedono gli esempi relativi alle basi di numerazione più comuni.

Figura 80.49. Conversione da base 16 a base 10.

da base sedici a base dieci

Figura 80.50. Conversione da base 8 a base 10.

da base otto a base dieci

Figura 80.51. Conversione da base 2 a base 10.

da base due a base dieci

80.3.2.1   Esercizio

Si traduca il valore 765,4328 in base dieci, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

decimale

80.3.2.2   Esercizio

Si traduca il valore AB,CD16 in base dieci, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

decimale

80.3.2.3   Esercizio

Si traduca il valore 101010,1100112 in base dieci, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

decimale

80.3.3   Conversione tra ottale, esadecimale e binario

Per quanto riguarda la conversione tra sistemi di numerazione ottale, esadecimale e binario, vale lo stesso principio dei numeri interi, con la differenza che occorre rispettare la separazione della parte intera da quella decimale. L'esempio della figura successiva dovrebbe essere abbastanza chiaro.

Figura 80.55. Conversione tra binario-ottale e binario-esadecimale.

conversione tra binario-ottale e binario-esadecimale

80.3.3.1   Esercizio

Si traduca il valore 76,558 in base due e quindi in base sedici, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

da base otto a base sedici

80.3.3.2   Esercizio

Si traduca il valore A7,C116 in base due e quindi in base otto, con l'uso di una calcolatrice comune e di un foglio di carta per annotare i calcoli intermedi, compilando poi lo schema successivo:

da base sedici a base otto

80.4   Operazioni elementari e sistema di rappresentazione binaria dei valori

È importante conoscere alcuni concetti legati ai calcoli più semplici, applicati al sistema binario; soprattutto il modo in cui si utilizza il complemento a due. Infatti, la memoria di un elaboratore consente di annotare esclusivamente delle cifre binarie, in uno spazio di dimensione prestabilita e fissa; pertanto, attraverso il complemento a due si ha la possibilità di gestire in modo «semplice» la rappresentazione dei numeri interi negativi.

80.4.1   Complemento alla base di numerazione

Dato un numero n, espresso in base b, con k cifre, il complemento alla base è costituito da bk-n.

Per esempio, il complemento alla base del numero 00 123 456 789 (espresso in base dieci utilizzando 11 cifre) è 99 876 543 211:

complemento alla base di 00123456789

Dall'esempio si deve osservare che la quantità di cifre utilizzata è determinante nel calcolo del complemento, infatti, il complemento alla base dello stesso numero, usando però solo nove cifre (123 456 789) è invece 876 543 211:

complemento alla base di 123456789

In modo analogo si procede con i valori aventi una base diversa; per esempio, il complemento alla base del numero binario 001100112, composto da otto cifre, è pari a 110011012:

complemento alla base di 00110011_(2)

Il calcolo del complemento alla base, nel sistema binario, avviene in modo molto semplice, se si trasforma in questo modo:

complemento alla base di 00110011_(2)

In pratica, si prende un numero composto da una quantità di cifre a uno, pari alla stessa quantità di cifre del numero di partenza; quindi si esegue la sottrazione, poi si aggiunge il valore uno al risultato finale. Si osservi però cosa accade con una situazione leggermente differente, per il calcolo del complemento alla base di 00110011002:

complemento alla base di 0011001100_(2)

Per eseguire una sottrazione, si può calcolare il complemento alla base del sottraendo (il valore da sottrarre), sommandolo poi al valore di partenza, trascurando il riporto eventuale. Per esempio, volendo sottrarre da 1 757 il valore 758, si può calcolare il complemento alla base di 0 758 (usando la stessa quantità di cifre dell'altro valore), per poi sommarla. Il complemento alla base di 0 758 è 9 242:

complemento alla base di 0758

Invece di eseguire la sottrazione, si somma il valore ottenuto a quello di partenza, ignorando il riporto:

1757+9242-10000

Infatti: 1 757-758=999.

80.4.1.1   Esercizio

Si determini il complemento alla base del valore 000012345610 (a dieci cifre), compilando lo schema successivo:

decimale

80.4.1.2   Esercizio

Si determini il complemento alla base del valore 999912345610 (a dieci cifre), compilando lo schema successivo:

decimale

80.4.2   Complemento a uno e complemento a due

Quando si fa riferimento a numeri in base due, il complemento alla base è più noto come «complemento a due» (che evidentemente è la stessa cosa). D'altro canto, il complemento a uno è ciò che è già stato descritto con l'esempio seguente, dove si ottiene a partire dal numero 00110011002:

complemento a uno di 0011001100_(2)

Si comprende intuitivamente che il complemento a uno si ottiene semplicemente invertendo le cifre binarie:

complemento a uno di 0011001100_(2)

Pertanto, il complemento a due di un numero binario si ottiene facilmente invertendo le cifre del numero di partenza e aggiungendo una unità al risultato.

80.4.2.1   Esercizio

Si determini il complemento a uno e il complemento a due del valore 00110010010001012, compilando gli schemi successivi:

complemento

complemento

80.4.2.2   Esercizio

Si determini il complemento a uno e il complemento a due del valore 11110011000101012, compilando gli schemi successivi:

complemento

complemento

80.4.3   Addizione binaria

L'addizione binaria avviene in modo analogo a quella del sistema decimale, con la differenza che si utilizzano soltanto due cifre numeriche: 0 e 1. Pertanto, si possono presentare solo i casi seguenti:

somma binaria

Segue l'esempio di una somma tra due numeri in base due:

somma in base due

80.4.4   Sottrazione binaria

La sottrazione binaria può essere eseguita nello stesso modo di quella che si utilizza nel sistema decimale. Come avviene nel sistema decimale, quando una cifra del minuendo (il numero di partenza) è minore della cifra corrispondente nel sottraendo (il numero da sottrarre), si prende a prestito una unità dalla cifra precedente (a sinistra), che così si somma al minuendo con il valore della base di numerazione. L'esempio seguente mostra una sottrazione con due numeri binari:

sottrazione binaria

Generalmente, la sottrazione binaria viene eseguita sommando il complemento alla base del sottraendo. Il complemento alla base di 001100112 con otto cifre è 110011012:

complemento alla base di 00110011_(2)

Pertanto, la sottrazione originale diventa una somma, dove si trascura il riporto:

sottrazione attraverso la somma

80.4.5   Moltiplicazione binaria

La moltiplicazione binaria si esegue in modo analogo a quella per il sistema decimale, con il vantaggio che è sufficiente sommare il moltiplicando, facendolo scorrere verso sinistra, in base al valore del moltiplicatore. Naturalmente, lo spostamento di un valore binario verso sinistra di n posizioni, corrisponde a moltiplicarlo per 2n. Si osservi l'esempio seguente dove si moltiplica 100110012 per 10112:

moltiplicazione binaria

80.4.6   Divisione binaria

La divisione binaria si esegue in modo analogo al procedimento per i valori in base dieci. Si osservi l'esempio seguente, dove si divide il numero 101102 (2210) per 1002 (410):

divisione binaria

In questo caso il risultato è 1012 (510), con il resto di 102 (210); ovvero 101,12 (5,510).

Intuitivamente si comprende che: si prende il divisore, senza zeri anteriori, lo si fa scorrere a sinistra in modo da trovarsi allineato inizialmente con il dividendo; se la sottrazione può avere luogo, si scrive la cifra 12 nel risultato; si continua con gli scorrimenti e le sottrazioni; al termine, il valore residuo è il resto della divisione intera.

80.4.7   Rappresentazione binaria di numeri interi senza segno

La rappresentazione di un valore intero senza segno coincide normalmente con il valore binario contenuto nella variabile gestita dall'elaboratore. Pertanto, una variabile della dimensione di 8 bit, può rappresentare valori da zero a 28-1:

000000002 (010)
000000012 (110)
000000102 (210)
...
111111102 (25410)
111111112 (25510)

80.4.8   Rappresentazione binaria di numeri interi con segno

Attualmente, per rappresentare valori interi con segno (positivo o negativo), si utilizza il metodo del complemento alla base, ovvero del complemento a due, dove il primo bit indica sempre il segno. Attraverso questo metodo, per cambiare di segno a un valore è sufficiente calcolarne il complemento a due.

Per esempio, se si prende un valore positivo rappresentato in otto cifre binarie come 000101002, pari a +2010, il complemento a due è: 111011002, pari a -2010 secondo questa convenzione. Per trasformare il valore negativo nel valore positivo corrispondente, basta calcolare nuovamente il complemento a due: da 111011002 si ottiene ancora 000101002 che è il valore positivo originario.

Con il complemento a due, disponendo di n cifre binarie, si possono rappresentare valori da -2(n-1) a +2(n-1)-1 ed esiste un solo modo per rappresentare lo zero: quando tutte le cifre binarie sono pari a zero. Infatti, rimanendo nell'ipotesi di otto cifre binarie, il complemento a uno di 000000002 è 111111112, ma aggiungendo una unità per ottenere il complemento a due si ottiene di nuovo 000000002, perdendo il riporto.

Si osservi che il valore negativo più grande rappresentabile non può essere trasformato in un valore positivo corrispondente, perché si creerebbe un traboccamento. Per esempio, utilizzando sempre otto bit (segno incluso), il valore minimo che possa essere rappresentato è 10000002, pari a -12810, ma se si calcola il complemento a due, si ottiene di nuovo lo stesso valore binario, che però non è valido. Infatti, il valore positivo massimo che si possa rappresentare in questo caso è solo +12710.

Figura 80.80. Confronto tra due valori interi con segno.

bin

Figura 80.81. Valori massimi rappresentabili con soli otto bit.

bin max

Il meccanismo del complemento a due ha il vantaggio di trasformare la sottrazione in una semplice somma algebrica.

80.4.8.1   Esercizio

Come si rappresenta il numero +10310, in una variabile binaria, a sedici cifre con segno?

sedici cifre con segno

80.4.8.2   Esercizio

Come si rappresenta il numero -10310, in una variabile binaria, a sedici cifre con segno?

sedici cifre con segno

80.4.8.3   Esercizio

Data una variabile a sedici cifre che rappresenta un numero con segno, contenente il valore 11111111111100012, si calcoli il complemento a due e poi il valore corrispondente in base dieci, specificando il segno:

sedici cifre con segno

decimale

80.4.8.4   Esercizio

Data una variabile a sedici cifre che rappresenta un numero con segno, contenente il valore 00000000001100012, si calcoli il valore corrispondente in base dieci:

decimale

Si calcoli quindi il complemento a due:

sedici cifre con segno

Supponendo di interpretare il valore binario ottenuto dal complemento a due, come se si trattasse di un'informazione priva di segno, si calcoli nuovamente il valore corrispondente in base dieci:

decimale

80.4.8.5   Esercizio

Data una variabile a dodici cifre binarie che rappresenta un numero con segno, leggendo il suo contenuto come se fosse una variabile priva di segno, si potrebbe determinare quel segno originale in base al valore che si ottiene. Si scrivano gli intervalli che riguardano valori positivi e valori negativi:

Intervallo che rappresenta valori positivi Intervallo che rappresenta valori negativi
 
 
 
 

80.4.8.6   Esercizio

Data una variabile a sedici cifre binarie che rappresenta un numero con segno, leggendo il suo contenuto come se fosse una variabile priva di segno, si potrebbe determinare quel segno originale in base al valore che si ottiene. Si scrivano gli intervalli che riguardano valori positivi e valori negativi:

Intervallo che rappresenta valori positivi Intervallo che rappresenta valori negativi
 
 
 
 

80.4.9   Cenni alla rappresentazione binaria di numeri in virgola mobile

Una forma diffusa per rappresentare dei valori molto grandi, consiste nell'indicare un numero con dei decimali moltiplicato per un valore costante elevato a un esponente intero. Per esempio, per rappresentare il numero 123 000 000 si potrebbe scrivere 123·106, oppure anche 0,123·109. Lo stesso ragionamento vale anche per valori molti piccoli; per esempio 0,000 000 123 che si potrebbe esprimere come 0,123·10-6.

Per usare una notazione uniforme, si può convenire di indicare il numero che appare prima della moltiplicazione per la costante elevata a una certa potenza come un valore che più si avvicina all'unità, essendo minore o al massimo uguale a uno. Pertanto, per gli esempi già mostrati, si tratterebbe sempre di 0,123·10n.

Per rappresentare valori a virgola mobile in modo binario, si usa un sistema simile, dove i bit a disposizione della variabile vengono suddivisi in tre parti: segno, esponente (di una base prestabilita) e mantissa, come nell'esempio che appare nella figura successiva.(1)

Figura 80.91. Ipotesi di una variabile a 16 bit per rappresentare dei numeri a virgola mobile.

virgola mobile

Nella figura si ipotizza la gestione di una variabile a 16 bit per la rappresentazione di valori a virgola mobile. Come si vede dallo schema, il bit più significativo della variabile viene utilizzato per rappresentare il segno del numero; i sette bit successivi si usano per indicare l'esponente (con segno) e gli otto bit finali per la mantissa (senza segno perché indicato nel primo bit), ovvero il valore da moltiplicare per una certa costante elevata all'esponente.

Quello che manca da decidere è come deve essere interpretato il numero della mantissa e qual è il valore della costante da elevare all'esponente indicato. Sempre a titolo di esempio, si conviene che il valore indicato nella mantissa esprima precisamente «0,mantissa» e che la costante da elevare all'esponente indicato sia 16 (ovvero 24), che si traduce in pratica nello spostamento della virgola di quattro cifre binarie alla volta.(2)

Figura 80.92. Esempio di rappresentazione del numero 0,051 513 671 875 (211·16-3), secondo le convenzioni stabilite. Si osservi che il valore dell'esponente è negativo ed è così rappresentato come complemento alla base (due) del valore assoluto relativo.

somma

Naturalmente, le convenzioni possono essere cambiate: per esempio il segno lo si può incorporare nella mantissa; si può rappresentare l'esponente attraverso un numero al quale deve essere sottratta una costante fissa; si può stabilire un valore diverso della costante da elevare all'esponente; si possono distribuire diversamente gli spazi assegnati all'esponente e alla mantissa.

80.5   Calcoli con i valori binari rappresentati nella forma usata negli elaboratori

Una volta chiarito il modo in cui si rappresentano comunemente i valori numerici elaborati da un microprocessore, in particolare per ciò che riguarda i valori negativi con il complemento a due, occorre conoscere in che modo si trattano o si possono trattare questi dati (indipendentemente dall'ordine dei byte usato).

80.5.1   Modifica della quantità di cifre di un numero binario intero

Un numero intero senza segno, espresso con una certa quantità di cifre, può essere trasformato in una quantità di cifre maggiore, aggiungendo degli zeri nella parte più significativa. Per esempio, il numero 01012 può essere trasformato in 000001012 senza cambiarne il valore. Nello stesso modo, si può fare una copia di un valore in un contenitore più piccolo, perdendo le cifre più significative, purché queste siano a zero, altrimenti il valore risultante sarebbe alterato.

Quando si ha a che fare con valori interi con segno, nel caso di valori positivi, l'estensione e la riduzione funzionano come per i valori senza segno, con la differenza che nella riduzione di cifre, la prima deve ancora rappresentare un segno positivo. Se invece si ha a che fare con valori negativi, l'aumento di cifre richiede l'aggiunta di cifre a uno nella parte più significativa, mentre la riduzione comporta l'eliminazione di cifre a uno nella parte più significativa, con il vincolo di mantenere inalterato il segno.

Figura 80.93. Aumento e riduzione delle cifre di un numero intero senza segno.

aumento e riduzione delle cifre binarie

Figura 80.94. Aumento e riduzione delle cifre di un numero intero positivo.

aumento e riduzione delle cifre binarie

Figura 80.95. Aumento e riduzione delle cifre di un numero intero negativo.

aumento e riduzione delle cifre binarie

80.5.1.1   Esercizio

Una variabile a otto cifre binarie, conviene un valore con segno, pari a 111000112. Questo valore viene copiato in una variabile a sedici cifre con segno. Indicare il valore che deve apparire nella variabile di destinazione.

sedici cifre con segno

80.5.1.2   Esercizio

Una variabile a sedici cifre binarie, contiene un valore con segno, pari a 00001111100011112. Questo valore viene copiato in una variabile a otto cifre con segno. Il risultato della copia è valido? Perché?

Il valore originale della variabile a sedici cifre con segno è pari a:

decimale

Il valore contenuto nella variabile a otto cifre con segno, potrebbe essere pari a:

decimale

80.5.1.3   Esercizio

Una variabile a otto cifre binarie, contiene un valore con segno, pari a 111000112. Questo valore viene copiato in una variabile a sedici cifre senza segno, ignorando il fatto che la variabile originale abbia un segno. Indicare il valore che appare nella variabile di destinazione dopo la copia.

sedici cifre con segno

Se successivamente si volesse considerare la variabile a sedici cifre usata per la destinazione della copia, come se fosse una variabile con segno, il valore che vi si potrebbe leggere al suo interno risulterebbe uguale a quello della variabile di origine?

80.5.2   Sommatorie con i valori interi con segno

Vengono proposti alcuni esempi che servono a dimostrare le situazioni che si presentano quando si sommano valori con segno, ricordando che i valori negativi sono rappresentati come complemento alla base del valore assoluto corrispondente.

Figura 80.100. Somma di due valori positivi che genera un risultato valido.

somma

Figura 80.101. Somma di due valori positivi, dove il risultato apparentemente negativo indica la presenza di un traboccamento.

somma

Figura 80.102. Somma di un valore positivo e di un valore negativo: il risultato è sempre valido.

somma

Figura 80.103. Somma di un valore positivo e di un valore negativo: in tal caso il risultato è sempre valido e se si manifesta un riporto, come in questo caso, va ignorato semplicemente.

somma

Figura 80.104. Somma di due valori negativi che produce un segno coerente e un riporto da ignorare.

somma

Figura 80.105. Somma di due valori negativi che genera un traboccamento, evidenziato da un risultato con un segno incoerente.

somma

Dagli esempi mostrati si comprende facilmente che la somma di due valori con segno va fatta ignorando il riporto, perché quello che conta è che il segno risultante sia coerente: se si sommano due valori positivi, perché il risultato sia valido deve essere positivo; se si somma un valore positivo con uno negativo il risultato è sempre valido; se si sommano due valori negativi, perché il risultato sia valido deve rimanere negativo.

80.5.2.1   Esercizio

Si esegua la somma tra due valori binari a otto cifre con segno, indicando anche il riporto eventuale: 010101012 + 011111102.

otto cifre con segno e con riporto

Il risultato della somma è valido?

80.5.2.2   Esercizio

Si esegua la somma tra due valori binari a otto cifre con segno, indicando anche il riporto eventuale: 110101012 + 011111102.

otto cifre con segno e con riporto

Il risultato della somma è valido?

80.5.2.3   Esercizio

Si esegua la somma tra due valori binari a otto cifre con segno, indicando anche il riporto eventuale: 110101012 + 100000012.

otto cifre con segno e con riporto

Il risultato della somma è valido?

80.5.3   Somme e sottrazioni con i valori interi senza segno

La somma di due numeri interi senza segno avviene normalmente, senza dare un valore particolare al bit più significativo, pertanto, se si genera un riporto, il risultato non è valido (salva la possibilità di considerarlo assieme al riporto). Se invece si vuole eseguire una sottrazione, il valore da sottrarre va «invertito», con il complemento a due, ma sempre evitando di dare un significato particolare al bit più significativo. Il valore «normale» e quello «invertito» vanno sommati come al solito, ma se il risultato non genera un riporto, allora è sbagliato, in quanto il sottraendo è più grande del minuendo.

Per comprendere come funziona la sottrazione, si consideri di volere eseguire un'operazione molto semplice: 1-1. Il minuendo (il primo valore) sia espresso come 000000012; il sottraendo (il secondo valore) che sarebbe uguale, va trasformato attraverso il complemento a due, diventando così pari a 111111112. A questo punto si sommano algebricamente i due valori e si ottiene 000000002 con riporto di uno. Il riporto di uno dà la garanzia che il risultato è corretto. Volendo provare a sottrarre un valore più grande, si vede che il riporto non viene ottenuto: 1-2. In questo caso il minuendo si esprime come nell'esempio precedente, mentre il sottraendo è 000000102 che si trasforma nel complemento a due 111111102. Se si sommano i due valori si ottiene semplicemente 111111112, senza riporto, ma questo valore che va inteso senza segno è evidentemente errato.

Figura 80.109. Sottrazione tra due numeri interi senza segno, dove il sottraendo ha un valore assoluto minore di quello del minuendo: la presenza del riporto conferma la validità del risultato.

sottrazione

Figura 80.110. Sottrazione tra due numeri interi senza segno, dove il sottraendo ha un valore assoluto maggiore di quello del minuendo: l'assenza di un riporto indica un risultato errato della sottrazione.

sottrazione

Sulla base della spiegazione data, c'è però un problema, dovuto al fatto che il complemento a due di un valore a zero dà sempre zero: se si fa la sottrazione con il complemento, il risultato è comunque corretto, ma non si ottiene un riporto.

Figura 80.111. Sottrazione con sottraendo a zero: non si ottiene riporto, ma il risultato è corretto ugualmente.

sottrazione

Per correggere questo problema, il complemento a due del numero da sottrarre, va eseguito in due fasi: prima si calcola il complemento a uno, poi si somma il minuendo al sottraendo complementato, aggiungendo una unità ulteriore. Le figure successive ripetono gli esempi già mostrati, attuando questo procedimento differente.

Figura 80.112. Il complemento a due viene calcolato in due fasi: prima si calcola il complemento a uno, poi si sommano il minuendo e il sottraendo invertito, più una unità.

sottrazione

sottrazione

Figura 80.114. Sottrazione con sottraendo a zero: calcolando il complemento a due attraverso il complemento a uno, si ottiene un riporto coerente.

sottrazione

80.5.3.1   Esercizio

Si esegua la somma tra due valori binari a otto cifre senza segno, indicando anche il riporto eventuale: 110101012 + 011101102.

otto cifre con segno e con riporto

Il risultato della somma è valido?

80.5.3.2   Esercizio

Si esegua la somma tra due valori binari a otto cifre senza segno, indicando anche il riporto eventuale: 110101012 + 111101102.

otto cifre con segno e con riporto

Il risultato della somma è valido?

80.5.3.3   Esercizio

Si esegua la sottrazione tra due valori binari a otto cifre senza segno, indicando anche il riporto eventuale: 110101012 - 111101102.

otto cifre con segno e con riporto

Il risultato della somma è valido?

80.5.3.4   Esercizio

Si esegua la sottrazione tra due valori binari a otto cifre senza segno, indicando anche il riporto eventuale: 110101012 - 000011112.

otto cifre con segno e con riporto

Il risultato della sottrazione è valido?

80.5.4   Somme e sottrazioni in fasi successive

Quando si possono eseguire somme e sottrazioni solo con una quantità limitata di cifre, mentre si vuole eseguire un calcolo con numeri più grandi della capacità consentita, si possono suddividere le operazioni in diverse fasi. La somma tra due numeri interi è molto semplice, perché ci si limita a tenere conto del riporto ottenuto nelle fasi precedenti. Per esempio, dovendo sommare 0101 1010 11002 a 1000 0101 01112 e potendo operare solo a gruppi di quattro bit per volta: si parte dal primo gruppo di bit meno significativo, 11002 e 01112, si sommano i due valori e si ottiene 00112 con riporto di uno; si prosegue sommando 10102 con 01012 aggiungendo il riporto e ottenendo 00002 con riporto di uno; si conclude sommando 01012 e 10002, aggiungendo il riporto della somma precedente e si ottiene così 11102. Quindi, il risultato è 1110 0000 00112.

Figura 80.119. Somma per fasi successive, tenendo conto del riporto.

somma

Nella sottrazione tra numeri senza segno, il sottraendo va trasformato secondo il complemento a due, quindi si esegue la somma e si considera che ci deve essere un riporto, altrimenti significa che il sottraendo è maggiore del minuendo. Quando si deve eseguire la sottrazione a gruppi di cifre più piccoli di quelli che richiede il valore per essere rappresentato, si può procedere in modo simile a quello che si usa con la somma, con la differenza che «l'assenza del riporto» indica la richiesta di prendere a prestito una cifra.

Per comprendere il procedimento è meglio partire da un esempio. In questo caso si utilizzano i valori già visti, ma invece di sommarli si vuole eseguire la sottrazione. Per la precisione, si intende prendere 1000 0101 01112 come minuendo e 0101 1010 11002 come sottraendo. Anche in questo caso si suppone di poter eseguire le operazioni solo a gruppi di quattro bit. Si esegue il complemento a due dei tre gruppetti di quattro bit del sottraendo, in modo indipendente, ottenendo: 10112, 01102, 01002. A questo punto si eseguono le somme, a partire dal gruppo meno significativo. La prima somma, 01112 + 01002, dà 10112, senza riporto, pertanto occorre prendere a prestito una cifra dal gruppo successivo: ciò significa che va eseguita la somma del gruppo successivo, sottraendo una unità dal risultato: 01012 + 01102 - 00012 = 10102. Anche per il secondo gruppo non si ottiene il riporto della somma, così, anche dal terzo gruppo di bit occorre prendere a prestito una cifra: 10002 + 10112 - 00012 = 00102. L'ultima volta la somma genera il riporto (da ignorare) che conferma la correttezza del risultato complessivo, ovvero che la sottrazione è avvenuta con successo.

Va però ricordato il problema legato allo zero, il cui complemento a due dà sempre zero. Se si cambiano i valori dell'esempio, lasciando come minuendo quello precedente, 1000 0101 01112, ma modificando il sottraendo in modo da avere le ultime quattro cifre a zero, 0101 1010 00002, il procedimento descritto non funziona più. Infatti, il complemento a due di 00002 rimane 00002 e se si somma questo a 01112 si ottiene lo stesso valore, ma senza riporti. In questo caso, nonostante l'assenza del riporto, il gruppo dei quattro bit successivi, del sottraendo, va trasformato con il complemento a due, senza togliere l'unità che sarebbe prevista secondo l'esempio precedente. In pratica, per poter eseguire la sottrazione per fasi successive, occorre definire un concetto diverso: il prestito (borrow) che non deve scattare quando si sottrae un valore pari a zero.

Se il complemento a due viene ottenuto passando per il complemento a uno, con l'aggiunta di una cifra, si può spiegare in modo più semplice il procedimento della sottrazione per fasi successive: invece di calcolare il complemento a due dei vari tronconi, si calcola semplicemente il complemento a uno e al gruppo meno significativo si aggiunge una unità per ottenere lì l'equivalente di un complemento a due. Successivamente, il riporto delle somme eseguite va aggiunto al gruppo adiacente più significativo, come si farebbe con la somma: se la sottrazione del gruppo precedente non ha bisogno del prestito di una cifra, si ottiene l'aggiunta una unità al gruppo successivo.

Figura 80.120. Sottrazione per fasi successive, tenendo conto del prestito delle cifre.

sottrazione

Figura 80.121. Verifica del procedimento anche in presenza di un sottraendo a zero.

sottrazione

La sottrazione per fasi successive funziona anche con valori che, complessivamente, hanno un segno. L'unica differenza sta nel modo di valutare il risultato complessivo: l'ultimo gruppo di cifre a essere considerato (quello più significativo) è quello che contiene il segno ed è il segno del risultato che deve essere coerente, per stabilire se ciò che si è ottenuto è valido. Pertanto, nel caso di valori con segno, il riporto finale si ignora, esattamente come si fa quando la sottrazione avviene in una fase sola, mentre l'esistenza o meno del traboccamento deriva dal confronto della cifra più significativa: se la sottrazione, dopo la trasformazione in somma con il complemento, implica la somma valori con lo stesso segno, il risultato deve ancora avere quel segno, altrimenti c'è il traboccamento.

Se si volessero considerare gli ultimi due esempi come la sottrazione di valori con segno, il minuendo si intenderebbe un valore negativo, mentre il sottraendo sarebbe un valore positivo. Attraverso il complemento si passa alla somma di due valori negativi, ma dal momento che si ottiene un risultato con segno positivo, ciò manifesta un traboccamento, ovvero un risultato errato, perché non contenibile nello spazio disponibile.

80.6   Scorrimenti, rotazioni, operazioni logiche

Le operazioni più semplici che si possono compiere con un microprocessore sono quelle che riguardano la logica booleana e lo scorrimento dei bit. Proprio per la loro semplicità è importante conoscere alcune applicazioni interessanti di questi procedimenti elaborativi.

80.6.1   Scorrimento logico

Lo scorrimento «logico» consiste nel fare scalare le cifre di un numero binario, verso sinistra (verso la parte più significativa) o verso destra (verso la parte meno significativa). Nell'eseguire questo scorrimento, da un lato si perde una cifra, mentre dall'altro si acquista uno zero.

Figura 80.122. Scorrimento logico a sinistra, perdendo le cifre più significative e scorrimento logico a destra, perdendo le cifre meno significative.

scorrimento

Lo scorrimento di una posizione verso sinistra corrisponde alla moltiplicazione del valore per due, mentre lo scorrimento a destra corrisponde a una divisione intera per due; scorrimenti di n posizioni rappresentano moltiplicazioni e divisioni per 2n. Le cifre che si perdono nello scorrimento a sinistra si possono considerare come il riporto della moltiplicazione, mentre le cifre che si perdono nello scorrimento a destra sono il resto della divisione.

80.6.1.1   Esercizio

Si esegua lo scorrimento logico a sinistra (di una sola cifra) del valore 110101012.

otto cifre

80.6.1.2   Esercizio

Si esegua lo scorrimento logico a destra (di una sola cifra) del valore 110101012.

otto cifre

80.6.2   Scorrimento aritmetico

Il tipo di scorrimento descritto nella sezione precedente, se utilizzato per eseguire moltiplicazioni e divisioni, va bene solo per valori senza segno. Se si intende fare lo scorrimento di un valore con segno, occorre distinguere due casi: lo scorrimento a sinistra è valido se il risultato non cambia di segno; lo scorrimento a destra implica il mantenimento del bit che rappresenta il segno e l'aggiunta di cifre uguali a quella che rappresenta il segno stesso.

Figura 80.125. Scorrimento aritmetico a sinistra e a destra, di un valore negativo.

scorrimento

80.6.2.1   Esercizio

Si esegua lo scorrimento aritmetico a sinistra (di una sola cifra) del valore con segno 010101012.

otto cifre con segno

Il risultato dello scorrimento è valido?

80.6.2.2   Esercizio

Si esegua lo scorrimento aritmetico a destra (di una sola cifra) del valore con segno 010101012.

otto cifre con segno

80.6.2.3   Esercizio

Si esegua lo scorrimento aritmetico a destra (di una sola cifra) del valore con segno 110101012.

otto cifre con segno

80.6.3   Moltiplicazione

La moltiplicazione si ottiene attraverso diverse fasi di scorrimento e somma di un valore, dove però il risultato richiede un numero doppio di cifre rispetto a quelle usate per il moltiplicando e il moltiplicatore. Il procedimento di moltiplicazione deve avvenire sempre con valori senza segno. Se i valori si intendono con segno, quando sono negativi occorre farne prima il complemento a due, in modo da portarli a valori positivi, quindi occorre decidere se il risultato va preso così come viene o se va invertito a sua volta con il complemento a due: se i valori moltiplicati hanno segno diverso tra loro, il risultato deve essere trasformato con il complemento a due per renderlo negativo, altrimenti il risultato è sempre positivo.

Figura 80.129. Moltiplicazione.

moltiplicazione

80.6.4   Divisione

La divisione si ottiene attraverso diverse fasi di scorrimento di un valore, che di volta in volta viene sottratto al dividendo, ma solo se la sottrazione è possibile effettivamente. Il procedimento di divisione deve avvenire sempre con valori senza segno. Se i valori si intendono con segno, quando sono negativi occorre farne prima il complemento a due, in modo da portarli a valori positivi, quindi occorre decidere se il risultato va preso così come viene o se va invertito a sua volta con il complemento a due: se dividendo e divisore hanno segni diversi tra loro, il risultato deve essere trasformato con il complemento a due per renderlo negativo, altrimenti il risultato è sempre positivo.

Figura 80.130. Divisione: i valori sono intesi senza segno.

Divisione

80.6.5   Rotazione

La rotazione è uno scorrimento dove le cifre che si perdono da una parte rientrano dall'altra. Esistono due tipi di rotazione; uno «normale» e l'altro che include nella rotazione il bit del riporto. Dal momento che la rotazione non si presta per i calcoli matematici, di solito non viene considerato il segno.

Figura 80.131. Rotazione normale.

rotazione

Figura 80.132. Rotazione con riporto.

rotazione

80.6.6   Operatori logici

Gli operatori logici si possono applicare anche a valori composti da più cifre binarie.

Figura 80.133. AND e OR.

and or

Figura 80.134. XOR e NOT.

xor e not

È importante osservare che l'operatore NOT esegue in pratica il complemento a uno di un valore.

Capita spesso di trovare in un sorgente scritto in un linguaggio assemblatore un'istruzione che assegna a un registro il risultato dell'operatore XOR su se stesso. Ciò si fa, evidentemente, per azzerarne il contenuto, quando, probabilmente, l'assegnamento esplicito di un valore a un registro richiede una frazione di tempo maggiore per la sua esecuzione.

Figura 80.135. XOR per azzerare i valori.

xor

80.6.6.1   Esercizio

Eseguire l'operazione seguente, considerando i valori privi di segno: 00100101010111112 AND 01100011110000112.

sedici cifre

80.6.6.2   Esercizio

Eseguire l'operazione seguente, considerando i valori privi di segno: 00100101010111112 OR 01100011110000112.

sedici cifre

80.6.6.3   Esercizio

Eseguire l'operazione seguente, considerando i valori privi di segno: 00100101010111112 XOR 01100011110000112.

sedici cifre

80.6.6.4   Esercizio

Eseguire l'operazione seguente, considerando i valori privi di segno: NOT 00100101010111112.

sedici cifre

80.7   Organizzazione della memoria

Nello studio del linguaggio C è importante avere un'idea di come venga gestita la memoria di un elaboratore, molto vicina a ciò che si percepirebbe usando direttamente il linguaggio della CPU.

80.7.1   Pila per salvare i dati

Quando si scrive con un linguaggio di programmazione molto vicino a quello effettivo del microprocessore, si ha normalmente a disposizione una pila di elementi omogenei (stack), usata per accumulare temporaneamente delle informazioni, da espellere poi in senso inverso. Questa pila è gestita attraverso un vettore, dove l'ultimo elemento (quello superiore) è individuato attraverso un indice noto come stack pointer e tutti gli elementi della pila sono comunque accessibili, in lettura e in sovrascrittura, se si conosce la loro posizione relativa.

Figura 80.140. Esempio di una pila che può contenere al massimo nove elementi, rappresentata nel modo tradizionale, oppure distesa, come si fa per i vettori. Gli elementi che si trovano oltre l'indice (lo stack pointer) non sono più disponibili, mentre gli altri possono essere letti e modificati senza doverli estrarre dalla pila.

pila

Per accumulare un dato nella pila (push) si incrementa di una unità l'indice e lo si inserisce in quel nuovo elemento. Per estrarre l'ultimo elemento dalla pila (pop) si legge il contenuto di quello corrispondente all'indice e si decrementa l'indice di una unità.

80.7.2   Chiamate di funzioni

I linguaggi di programmazione più vicini alla realtà fisica della memoria di un elaboratore, possono disporre solo di variabili globali ed eventualmente di una pila, realizzata attraverso un vettore, come descritto nella sezione precedente. In questa situazione, la chiamata di una funzione può avvenire solo passando i parametri in uno spazio di memoria condiviso da tutto il programma. Ma per poter generalizzare le funzioni e per consentire la ricorsione, ovvero per rendere le funzioni rientranti, il passaggio dei parametri deve avvenire attraverso la pila in questione.

Per mostrare un esempio che consenta di comprendere il meccanismo, si può osservare l'esempio seguente, schematizzato attraverso una pseudocodifica: la funzione SOMMA prevede l'uso di due parametri (ovvero due argomenti nella chiamata) e di una variabile «locale». Per chiamare la funzione, occorre mettere i valori dei parametri nella pila; successivamente, si dichiara la stessa variabile locale nella pila. Si consideri che il programma inizia e finisce nella funzione MAIN, all'interno della quale si fa la chiamata della funzione SOMMA:

SOMMA (X, Y)
    LOCAL Z INTEGER
    Z := X + Y
    RETURN Z
END SOMMA

MAIN ()
    LOCAL A INTEGER
    LOCAL B INTEGER
    LOCAL C INTEGER
    A := 3
    B := 4
    C := SOMMA (A, B)
END MAIN

Nel disegno successivo, si schematizza ciò che accade nella pila (nel vettore che rappresenta la pila dei dati), dove si vede che inizialmente c'è una situazione indefinita, con l'indice «sp» (stack pointer) in una certa posizione. Quando viene eseguita la chiamata della funzione, automaticamente si incrementa la pila inserendo gli argomenti della chiamata (qui si mettono in ordine inverso, come si fa nel linguaggio C), mettendo in cima anche altre informazioni che nello schema non vengono chiarite (nel disegno appare un elemento con dei puntini di sospensione).

Figura 80.142. Situazione della pila nelle varie fasi della chiamata della funzione SOMMA.

pila

La variabile locale «Z» viene allocata in cima alla pila, incrementando ulteriormente l'indice «sp». Al termine, la funzione trasmette in qualche modo il proprio risultato (tale modalità non viene chiarita qui e dipende dalle convenzioni di chiamata) e la pila viene riportata alla sua condizione iniziale.

Dal momento che l'esempio di programma contiene dei valori particolari, il disegno di ciò che succede alla pila dei dati può essere reso più preciso, mettendo ciò che contengono effettivamente le varie celle della pila.

Figura 80.143. Situazione della pila nelle varie fasi della chiamata della funzione SOMMA, osservando i contenuti delle varie celle.

pila

80.7.3   Variabili e array

Con un linguaggio di programmazione molto vicino alla realtà fisica dell'elaboratore, la memoria centrale viene vista come un vettore di celle uniformi, corrispondenti normalmente a un byte. All'interno di tale vettore si distendono tutti i dati gestiti, compresa la pila descritta nelle prime sezioni del capitolo. In questo modo, le variabili in memoria si raggiungono attraverso un indirizzo che individua il primo byte che le compone ed è compito del programma il sapere di quanti byte sono composte complessivamente.

Figura 80.144. Esempio di mappa di una memoria di soli 256 byte, dove sono evidenziate alcune variabili. Gli indirizzi dei byte della memoria vanno da 0016 a FF16.

memoria

Nel disegno in cui si ipotizza una memoria complessiva di 256 byte, sono state evidenziate alcune aree di memoria:

Indirizzo Dimensione Indirizzo Dimensione
5416 4 byte 5816 4 byte
5C16 2 byte 5E16 4 byte
6216 8 byte 6A16 4 byte
6E16 1 byte 6F16 8 byte

Con una gestione di questo tipo della memoria, la rappresentazione degli array richiede un po' di impegno da parte del programmatore. Nella figura successiva si rappresenta una matrice a tre dimensioni; per ora si ignorino i codici numerici associati alle celle visibili.

Figura 80.146. La matrice a tre dimensioni che si vuole rappresentare, secondo un modello spaziale. I numeri che appaiono servono a trovare successivamente l'abbinamento con le celle di memoria utilizzate.

matrice

Dal momento che la rappresentazione tridimensionale rischia di creare confusione, quando si devono rappresentare matrici che hanno più di due dimensioni, è più conveniente pensare a strutture ad albero. Nella figura successiva viene tradotta in forma di albero la matrice rappresentata precedentemente.

Figura 80.147. La matrice a tre dimensioni che si vuole rappresentare, tradotta in uno schema gerarchico (ad albero).

matrice

Si suppone di rappresentare la matrice in questione nella memoria dell'elaboratore, dove ogni elemento terminale contiene due byte. Supponendo di allocare l'array a partire dall'indirizzo 7716 nella mappa di memoria già descritta, si potrebbe ottenere quanto si vede nella figura successiva. A questo punto, si può vedere la corrispondenza tra gli indirizzi dei vari componenti dell'array e le figure già mostrate.

Figura 80.148. Esempio di mappa di memoria in cui si distende un array che rappresenta una matrice a tre dimensioni con tre elementi contenenti ognuno due elementi che a loro volta contengono quattro elementi da due byte.

memoria

Si pone quindi il problema di scandire gli elementi dell'array. Considerando che array ha dimensioni «3,2,4» e definendo che gli indici partano da zero, l'elemento [0,0,0] corrisponde alla coppia di byte che inizia all'indirizzo 7716, mentre l'elemento [2,1,3] corrisponde all'indirizzo A516. Per calcolare l'indirizzo corrispondente a un certo elemento occorre usare la formula seguente, dove: le variabili I, J, K rappresentano la dimensioni dei componenti; le variabili i, j, k rappresentano l'indice dell'elemento cercato; la variabile A rappresenta l'indirizzo iniziale dell'array; la variabile s rappresenta la dimensione in byte degli elementi terminali dell'array.

A + (i*J*K*s + j*K*s + k*s)

A + (i*J*K*s + j*K*s + k*s)

Si vuole calcolare la posizione dell'elemento 2,0,1. Per facilitare i conti a livello umano, si converte l'indirizzo iniziale dell'array in base dieci: 7716 = 11910:

A + (i*J*K*s + j*K*s + k*s)

Il valore 15310 si traduce in base sedici in 9916, che corrisponde effettivamente all'elemento cercato: terzo elemento principale, all'interno del quale si cerca il primo elemento, all'interno del quale si cerca il secondo elemento finale.

80.7.3.1   Esercizio

Una certa variabile occupa quattro unità di memoria, a partire dall'indirizzo 2F16. Qual è l'indirizzo dell'ultima unità di memoria occupata dalla variabile?

Indirizzo iniziale Indirizzo dell'ultima unità di memoria della variabile
2F16  
 
 
 

80.7.3.2   Esercizio

In memoria viene rappresentato un array di sette elementi da quattro unità di memoria ciascuno. Se l'indirizzo iniziale di questo array è 1716, qual è l'indirizzo dell'ultima cella di memoria usata da questo array?

Indirizzo iniziale Indirizzo dell'ultima unità di memoria dell'array
1716  
 
 
 

80.7.3.3   Esercizio

In memoria viene rappresentato un array di elementi da quattro unità di memoria ciascuno. Se l'indirizzo iniziale di questo array è 1716, qual è l'indirizzo iniziale del secondo elemento dell'array?

Indirizzo iniziale Indirizzo del secondo elemento dell'array
1716  
 
 
 

80.7.3.4   Esercizio

In memoria viene rappresentato un array di n elementi da quattro unità di memoria ciascuno. Se l'indirizzo iniziale di questo array è 1716, a quale elemento punta l'indirizzo 2B16?

Indirizzo iniziale Indirizzo dato Elemento dell'array
1716 2B16  
 
 
 

80.7.3.5   Esercizio

In memoria viene rappresentato un array di n elementi da quattro unità di memoria ciascuno. Se l'indirizzo iniziale di questo array è 1716, l'indirizzo 2216 potrebbe puntare all'inizio di un certo elemento di questo?

80.7.4   Ordine dei byte

Come già descritto in questo capitolo, normalmente l'accesso alla memoria avviene conoscendo l'indirizzo iniziale dell'informazione cercata, sapendo poi per quanti byte questa si estende. Il microprocessore, a seconda delle proprie caratteristiche e delle istruzioni ricevute, legge e scrive la memoria a gruppetti di byte, più o meno numerosi. Ma l'ordine dei byte che il microprocessore utilizza potrebbe essere diverso da quello che si immagina di solito.

Figura 80.156. Confronto tra big endian e little endian.

little endian, big endian

A questo proposito, per quanto riguarda la rappresentazione dei dati nella memoria, si distingue tra big endian, corrispondente a una rappresentazione «normale», dove il primo byte è quello più significativo (big), e little endian, dove la sequenza dei byte è invertita (ma i bit di ogni byte rimangono nella stessa sequenza standard) e il primo byte è quello meno significativo (little). La cosa importante da chiarire è che l'effetto dell'inversione nella sequenza porta a risultati differenti, a seconda della quantità di byte che compongono l'insieme letto o scritto simultaneamente dal microprocessore, come si vede nella figura.

80.7.4.1   Esercizio

In memoria viene rappresentata una variabile di 2 byte di lunghezza, a partire dall'indirizzo 2116, contenente il valore 11111100110000002. Se la CPU accede alla memoria secondo la modalità big endian, che valore si legge all'indirizzo 2116 se si pretende di trovare una variabile da un solo byte?

bit

Cosa si legge, invece, se la CPU accede alla memoria secondo la modalità little endian (invertita)?

bit

80.7.5   Stringhe, array e puntatori

Le stringhe sono rappresentate in memoria come array di caratteri, dove il carattere può impiegare un byte o dimensioni multiple (nel caso di UTF-8, un carattere viene rappresentato utilizzando da uno a quattro byte, a seconda del punto di codifica raggiunto). Il riferimento a una stringa viene fatto come avviene per gli array in generale, attraverso un puntatore all'indirizzo della prima cella di memoria che lo contiene; tuttavia, per non dovere annotare la dimensione di tale array, di norma si conviene che la fine della stringa sia delimitata da un byte a zero, come si vede nell'esempio della figura.

Figura 80.159. Stringa conclusa da un byte a zero (zero terminated string), a cui viene fatto riferimento per mezzo di una variabile che contiene il suo indirizzo iniziale. La stringa contiene il testo Ciao amore., secondo la codifica ASCII.

stringa

Nella figura si vede che la variabile scalare collocata all'indirizzo 5316 contiene un valore da intendere come indirizzo, con il quale si fa riferimento al primo byte dell'array che rappresenta la stringa (in posizione 7816). La variabile collocata in 5316 assume così il ruolo di variabile puntatore e, secondo il modello ridotto di memoria della figura, è sufficiente un solo byte per rappresentare un tale puntatore, dal momento che servono soltanto valori da 0016 a FF16.

80.7.5.1   Esercizio

In memoria viene rappresentata la stringa «Ciao a tutti». Sapendo che ogni carattere utilizza un solo byte e che la stringa è terminata regolarmente con il codice nullo di terminazione (0016), quanti byte occupa la stringa in memoria?

80.7.5.2   Esercizio

In memoria viene rappresentata la stringa «Ciao a tutti» (come nell'esercizio precedente). Sapendo che la stringa inizia all'indirizzo 3F16, a quale indirizzo si trova la lettera «u» di «tutti»?

80.7.5.3   Esercizio

Se la memoria dell'elaboratore consente di raggiungere indirizzi da 000016 a FFFF16, quanto deve essere grande una variabile scalare che si utilizza come puntatore? Si indichi la quantità di cifre binarie.

80.7.6   Utilizzo della memoria

La memoria dell'elaboratore viene utilizzata sia per contenere i dati, sia per il codice del programma che li utilizza. Ogni programma ha un proprio spazio in memoria, che può essere reale o virtuale; all'interno di questo spazio, la disposizione delle varie componenti potrebbe essere differente. Nei sistemi che si rifanno al modello di Unix, nella parte più «bassa» della memoria risiede il codice che viene eseguito; subito dopo vengono le variabili globali del programma, mentre dalla parte più «alta» inizia la pila dei dati che cresce verso indirizzi inferiori. Si possono comunque immaginare combinazioni differenti di tale organizzazione, pur rispettando il vincolo di avere tre zone ben distinte per il loro contesto (codice, dati, pila); tuttavia, ci sono situazioni in cui i dati si trovano mescolati al codice, per qualche ragione.

Figura 80.160. Esempio di disposizione delle componenti di un programma in esecuzione in memoria, secondo il modello Unix.

memoria

80.8   Riferimenti

80.9   Soluzioni agli esercizi proposti

Esercizio Soluzione
80.1.2.1
111100112 = 24310.
80.1.2.2
011001102 = 10210.
80.1.3.1
13578 = 75110.
80.1.3.2
75318 = 392910.
80.1.4.1
15AC16 = 554810.
80.1.4.2
CF5816 = 5308010.
80.2.1.1
123410 = 23228.
80.2.1.2
432110 = 103418.
80.2.2.1
4422110 = ACBD16.
80.2.2.2
1224410 = 2FD416.
80.2.3.1
123410 = 100110100102.
80.2.3.2
432110 = 10000111000012.
80.2.4.1
ABC16 = 1010101111002 = 52748.
80.2.4.2
76558 = 1111101011012 = FAD16.
80.3.1.1
43,2110 = 53,153418.
80.3.1.2
765,432110 = 2FD,6E9E116.
80.3.1.3
21,1110 = 10101,000112.
80.3.2.1
765,4328 = 501,5507810.
80.3.2.2
AB,CD16 = 171,8007810.
80.3.2.3
101010,1100112 = 42,7968710.
80.3.3.1
76,558 = 00111110,101101002 = 3E,B416.
80.3.3.2
A7,C116 = 010100111,1100000102 = 247,6028.
80.4.1.1
complemento alla base di 000012345610 = 999987654410.
80.4.1.2
complemento alla base di 999912345610 = 000087654410.
80.4.2.1
complemento a uno di 00110010010001012 = 11001101101110102; complemento a due = 11001101101110112.
80.4.2.2
complemento a uno di 11110011000101012 = 00001100111010102; complemento a due = 00001100111010112.
80.4.8.1
+10310 = 00000000011001112.
80.4.8.2
-10310 = 11111111100110012.
80.4.8.3
11111111111100012 = -1510; complemento a due = 00000000000011112.
80.4.8.4
00000000001100012 = +4910; complemento a due = 11111111110011112; se 11111111110011112 fosse inteso senza segno sarebbe uguale a 6548710.
80.4.8.5
da 010 a 204710 indica valori positivi; da 204810 a 409510 indica valori negativi.
80.4.8.6
da 010 a 3276710 indica valori positivi; da 3276810 a 6553510 indica valori negativi.
80.5.1.1
111000112 con segno si traduce, a sedici cifre in 11111111111000112.
80.5.1.2
00001111100011112 con segno equivale a +398310, mentre 100011112 con segno equivale a -11310; se poi si volesse supporre che la riduzione di cifre mantenga il segno, si avrebbe 000011112 che equivale a +1510. Pertanto, in questo caso, la riduzione di cifre non può essere valida.
80.5.1.3
111000112 con segno equivale a -2910; copiando questo valore in una variabile senza segno, a sedici cifre, si ottiene 00000000111000112, pari a 22710. Se, successivamente, si interpreta il nuovo valore con segno, si ottiene +22710, che non corrisponde in alcun modo al valore originale.
80.5.2.1
010101012 con segno + 011111102 con segno = 110100112 con riporto di zero. Il risultato non è valido perché, pur sommando due valori positivi, il segno è diventato negativo.
80.5.2.2
110101012 con segno + 011111102 con segno = 010100112 con riporto di uno. Il risultato della somma tra un numero positivo e un numero negativo è sempre valido.
80.5.2.3
110101012 con segno + 100000012 con segno = 010101102 con riporto di uno. Il risultato non è valido perché si sommano due numeri negativi, ma il risultato è positivo.
80.5.3.1
110101012 + 100000012 = 010010112 con riporto di uno. Il risultato non è valido perché c'è un riporto.
80.5.3.2
110101012 + 111101102 = 110010112 con riporto di uno. Il risultato non è valido perché c'è un riporto.
80.5.3.3
La sottrazione 110101012 - 111101102 va trasformata nella somma 110101012 + 000010102 = 110111112 senza riporto. Il risultato non è valido perché manca il riporto (d'altra parte si sta sottraendo un valore più grande del minuendo, pertanto il risultato senza segno non può essere valido
80.5.3.4
La sottrazione 110101012 - 000011112 va trasformata nella somma 110101012 + 111100012 = 110001102 con riporto di uno. Il risultato è valido perché si ha un riporto
80.6.1.1
Lo scorrimento logico a sinistra di 110101012, di una sola cifra, è pari a 101010102.
80.6.1.2
Lo scorrimento logico a destra di 110101012, di una sola cifra, è pari a 011010102.
80.6.2.1
Lo scorrimento aritmetico a sinistra di 010101012 (con segno), di una sola cifra, è pari a 101010102, ma si ottiene un cambiamento di segno e il risultato non è valido.
80.6.2.2
Lo scorrimento aritmetico a destra di 010101012 (con segno), di una sola cifra, è pari a 001010102. Il risultato è valido, in quanto è stato possibile preservare il segno e il valore ottenuto è pari alla divisione per due di quello originale.
80.6.2.3
Lo scorrimento aritmetico a destra di 110101012 (con segno), di una sola cifra, è pari a 111010102. Il risultato è valido, in quanto è stato possibile preservare il segno e il valore ottenuto è pari alla divisione per due di quello originale.
80.6.6.1
00100101010111112 AND 01100011110000112 = 00100001010000112.
80.6.6.2
00100101010111112 OR 01100011110000112 = 01100111110111112.
80.6.6.3
00100101010111112 XOR 01100011110000112 = 01000110100111002.
80.6.6.4
NOT 00100101010111112 = 11011010101000002.
80.7.3.1
L'ultima unità di memoria usata dalla variabile scalare si trova all'indirizzo 3216.
80.7.3.2
L'array è lungo 28 unità di memoria e termina all'indirizzo 3216 incluso.
80.7.3.3
L'indirizzo del secondo elemento dell'array è 1B16.
80.7.3.4
L'indirizzo 2B16 punta al sesto elemento dell'array.
80.7.3.5
L'indirizzo 2216 individua una cella di memoria del terzo elemento dell'array, ma non trattandosi dell'inizio di tale elemento, non è utile come indice dello stesso.
80.7.4.1
In modalità big endian, la variabile che contiene 11111100110000002, se viene letta come se fosse costituita da un solo byte, darebbe 111111002, ovvero la porzione più significativa della stessa. Invece, in modalità little endian, ciò che si leggerebbe sarebbe la porzione meno significativa: 110000002.
80.7.5.1
La stringa «Ciao a tutti», terminata regolarmente, occupa 13 byte.
80.7.5.2
Sapendo che la stringa «Ciao a tutti» inizia all'indirizzo 3F16, la lettera «u» si trova all'indirizzo 4716.
80.7.5.3
La variabile che consenta di rappresentare puntatori per indirizzi da 000016 a FFFF16, deve essere almeno da 16 bit (sedici cifre binarie).

1) Nel contesto riferito alla definizione di un numero in virgola mobile, si possono usare indifferentemente i termini mantissa o significante, così come sono indifferenti i termini caratteristica o esponente.

2) Si osservi che lo standard IEEE 754 utilizza una «mantissa normalizzata» che indica la frazione di valore tra uno e due: «1,mantissa.

«a2» 2013.11.11 --- Copyright © Daniele Giacomini -- appunti2@gmail.com http://informaticalibera.net