CAPITOLO 2 - Tipi di dati e operatori in C++.

2.1 – Rappresentazione dei numeri interi

Nel primo paragrafo abbiamo introdotto il bit come unità fondamentale dell’informazione. Il bit può assumere soltanto i valori 0 ed 1, per cui si afferma comunemente che il computer utilizza il sistema binario. Dobbiamo allora chiederci: come si scrive un numero in forma binaria?

La notazione decimale si serve di 10 cifre, da 0 a 9: diremo che la rappresentazione del numero è in base 10. Un numero in notazione decimale utilizza il sistema di numerazione posizionale: una cifra assume valore diverso secondo la posizione che occupa. Nel numero 555 la prima cifra vale 500, la seconda 50, la terza 5. In generale, dette cn, cn-1, cn-2, ……. c1, c0 le n+1 cifre del numero esso viene rappresentato col valore

funzioni

Ad esempio il numero 1232 in base 10 è dato da

(1232)10= 1×103 + 2×102 + 3×101 + 2×100

Osserviamo che poiché in questo paragrafo utilizzeremo più basi, scriveremo la base in basso a destra del numero.

La scrittura precedente può essere generalizzata; detta B una base qualunque, possiamo scrivere

funzioni

dove ogni c è un simbolo che rappresenta le cifre comprese tra 0 a B-1.

La numerazione binaria si avvale soltanto delle cifre 0 ed 1, per cui il numero (11011)2 può essere trasformato facilmente in base 10 :

(11011)2 = (1×24 + 1×23 + 0×22 + 1×21 + 1×20)10 = (16 + 8 + 2 + 1)10 = (27)10

Nulla vieta di utilizzare la formula generale con un’altra base diversa da 2 e 10. Se la base è 8 ogni cifra deve essere compresa tra 0 e 7; ad esempio il numero (1056)8 diventa in base 10

(1056)8 = (1×83 + 0×82 + 5×81 + 6×80)10 = (512 + 40 + 6)10 = (558)10

Se la base è 16 dobbiamo prima trovare dei simboli per indicare i numeri da 10 a 15; poniamo A=10; B=11 ; C=12; D=13; E=14; F=15 per cui il numero (2A0B)16 diventa in base 10

(2A0B)16 = (2×163 + 10×162 + 0×161 + 11×160)10 = (4096 + 2560 + 11)10 = (6667)10

Una volta introdotti i vari tipi di numerazione, li abbiamo trasformati tutti in base 10. Come si esegue il processo inverso?

Cominciamo col determinare la modalità di passaggio dalla numerazione decimale alla binaria. Prendiamo in considerazione il numero 25 e dividiamolo per 2 più volte: ogni volta il quoziente viene sempre diviso per 2 finché l’ultimo quoziente è minore del divisore e diverso da zero ( e quindi 1).

25 : 2

1     12 : 2

         0    6 : 2

                  3 : 2

                     1     1        

Il numero in base 2 si ottiene componendo i resti e l'ultimo quoziente in ordine inverso: (11001)2=(25)10.

Tale tecnica è generalizzabile: calcoliamo 25 in base 3, 4 e 16.

Base 3 Base 4 Base 16
25 : 3

 1    8 : 3

       2   2

25 : 4

1    6  : 4

        2    1

25 : 16

 9     1

Quindi si ha:

(25)10=(221)3=(121)4=(19)16

Come si eseguono le operazioni con i numeri con base diversa da 10?

Senza dilungarci troppo, prenderemo in considerazione soltanto la somma in base 2. A questo scopo, osserviamo che valgono le seguenti regole:

0 + 0 = 0

1 + 0 = 1

0 + 1 = 1

1 + 1 = 0 con il riporto di 1 nella colonna che precede la somma.

Per esempio, sommiamo i due numeri in base 2, 11011 e 1001:

 11 11( qui abbiamo scritto eventuali riporti)

   11011 +

     1001 =

100100

Dopo queste necessarie premesse, accenniamo alla rappresentazione dei numeri interi e reali nel calcolatore.

Innanzitutto è necessario determinare il numero di byte disponibili per la memorizzazione. Se, per esempio, abbiamo disponibili 2 byte, 16 bit, per rappresentare un numero intero, allora possiamo rappresentare

216 = 65536

numeri interi positivi distinti, da 0 a 65535. Volendo rappresentare anche i numeri negativi, dobbiamo dimezzare tale quantità, per cui i numeri interi relativi gestibili con 16 bit sono inclusi nell’intervallo

[ -32767, 32768 ]

In generale, se m è il numero di bit, il massimo numero intero senza segno rappresentabile è

2m - 1.

Quindi con 8 bit si può rappresentare al massimo un numero pari a 28 - 1=255, con 16 bit 216 - 1=65535, con 32 bit 232 - 1=4294967295.

Per rappresentare un numero con segno si è pensato di scegliere come segno il primo bit ( 0 per il + ed 1 per il - ). In questo modo possiamo rappresentare i numeri compresi tra

(01111111)2 = 127 e (11111111)2 = -127

Ad una attenta osservazione sorgono subito due problemi:

1) con questa rappresentazione abbiamo che lo zero si può scrivere in due modi distinti tra loro +0 00000000 e -0 10000000 ( ricordiamo che 0 ed 1 rappresentano rispettivamente i segni + e - ); ogni confronto di un numero con zero dovrebbe essere raddoppiato: significherebbe doverlo fare sia con +0 che con –0!!

2) consideriamo ad esempio i due numeri (01010000)2 = 96 e (11010000)2 = -96 ed il numero (00001000)2 = 8 ed eseguiamo le somme in binario

96+       01010000+

8            00001000

104        01011000 = 104   corretta

-96+       11010000+

   8          00001000

-88          11011000 = -104   errata

La somma in binario produce un risultato scorretto!!!

L’idea per risolvere il problema risiede nel fatto che asserire che un numero x è negativo significa affermare che dista x a sinistra dello zero; così –8 dista 8 unità dallo zero verso sinistra

immagine13

Quindi, mentre (x)10 ( x in base 10) può essere rappresentato facilmente in base 2 come (x)2 ( x in base 2), il numero negativo (-x)10 deve essere rappresentato come

(-x)10 = ( 2n - x )2

cioè il numero con n bit deve essere rappresentato in base 2 come distante x dal massimo valore 2n.

Tale rappresentazione è detta complemento a due.

Per determinare le operazioni da eseguire per il complemento a due aggiungiamo +1 e –1 al secondo membro dell’ultima uguaglianza, ottenendo

(-x)10 = ( 2n - 1 )2 +(-x)2 +1

La quantità ( 2n - 1 )2 +(-x)2 rappresenta il complemento ad uno del numero x2 in quanto scambia tutti gli 1 con 0 e viceversa. Infatti, mentre 2n è formato da un 1 seguito da tutti 0, ( 2n - 1 )2 è dato da uno zero seguito da (n-1) cifre 1; poiché (-x)2 è il sottraendo (riportato di seguito in grassetto) e poiché la sottrazione può dare solo i seguenti risultati

1 – 0 = 1 e 1 - 1 = 0

il risultato dell’operazione ( 2n - 1 )2 -x2 scambia semplicemente gli 1 con gli zeri.

Infine per ottenere il complemento a due è sufficiente sommare +1: il numero finale così ottenuto rappresenterà il valore negativo del numero iniziale. Calcoliamo il complemento a due di 96:

96 = (01010000 )2 →complemento ad uno →10101111 → sommiamo 1

otteniamo il complemento a due: 10110000 che rappresenta –96

Ripetiamo l’operazione –96 + 8 con la nuova rappresentazione di –96:

-96 +  10110000 +

   8      00001000

-88      10111000 = -88 esatta

In C++ i numeri interi ( dichiarazione int ) occupano in genere 4 byte di memoria ( 32 bit ) e possono assumere dei valori compresi tra –2.147.483.468 e +2.147.483.467.

2.2 - Rappresentazione dei numeri reali.

Poiché è finito l’insieme dei numeri rappresentabili con un calcolatore, dobbiamo determinare il modo più conveniente possibile per associare ad un numero reale un numero di macchina. Indichiamo con numero di macchina una configurazione finita di cifre binarie. Dobbiamo fare in modo che l’insieme dei numeri che possiamo rappresentare siano distribuiti sulla retta reale in modo tale che il massimo errore relativo sia il più piccolo possibile.

Un numero può essere rappresentato da

x=±(c1c2c3......cn.....)Bp   con c1≠0

dove

B = base della rappresentazione

p = caratteristica

c1c2c3......cn..... sono le cifre della rappresentazione finita ( detta mantissa)

Esempio.

Il numero x=3/7000 può essere rappresentato con

x = 0.000428571428571…….. rappresentazione non normalizzata

x = (0.428571428571…..) 10-3 rappresentazione normalizzata

Dall’esempio risulta che la normalizzazione consente una migliore approssimazione del numero quando si ha a disposizione soltanto un numero finito di cifre. Nel caso in cui le cifre a disposizione fossero solo 7 allora avremo che il numero 3/7000 verrebbe rappresentato con

0.0004285 non normalizzato e 0.4285714 normalizzato.

Se fissiamo la base B, il numero di cifre F, il minimo dell’esponente m ed il massimo esponente M, l’insieme dei numeri reali rappresentabili è detto insieme dei numeri-macchina ed è dato da

x=±(c1c2c3......cF)Bh  

con m≤ h ≤ M

In questo rappresentazione è escluso lo zero che non è rappresentabile con una mantissa diversa da zero. Lo zero si può rappresentare con una mantissa nulla e con l’esponente m.

Tenendo presente le considerazioni fin qui svolte, osserviamo che i numeri reali sono rappresentati in memoria nella modalità a virgola mobile ( floating point ) in singola e doppia precisione. La rappresentazione in virgola mobile è di tipo esponenziale: il numero x ha un bit per il segno s, alcuni bit per la mantissa m ed altri per l’esponente e

x = s 0,m 10e

In generale, un numero in singola precisione, float, ha disponibile 32 bit (4 byte) così suddivisi:

1 bit per il segno, 8 bit per l’esponente e 23 per la mantissa

Potendo l’esponente essere sia positivo che negativo, con 8 bit è possibile utilizzare i numeri compresi nell’intervallo

[ -127, 128 ] per cui l’esponente varia tra [ 2-127, 2128 ] che, in base 10, corrisponde ad un esponente incluso nell’intervallo [ 10-37, 1038 ].

Poiché

223 = circa 106,9

la mantissa m potrà avere al massimo 6 o 7 cifre significative.

I numeri reali in doppia precisione, double, occupano 64 bit (8 byte) così suddivisi:

1 bit per il segno, 11 bit per l’esponente e 52 per la mantissa

Lasciamo al lettore, come esercizio, di determinare il numero di cifre significative ed il massimo numero rappresentabile in doppia precisione.

Osservazione.
Pur scegliendo la migliore rappresentazione possibile, i numeri reali conservano un problema di fondo: scelti due numeri reali anche molto "vicini" tra loro è possibile sempre determinarne un altro tra essi compreso. Ciò comporta che ad un certo punto quel numero non sarà più rappresentabile! Prendiamo come esempio i numeri 0 ed 1: dividiamo sempre per 2 (base dei numeri) la loro differenza, ottenendo 1/2, 1/4, 1/8,.............. Ad un certo punto quel numero non è più rappresentabile dalla macchina: si comporta come se fosse nullo!!! Se, infatti, lo sommiamo ad 1 otteniamo ancora 1!!!

Chiameremo epsilon macchina il numero ottenuto dividendo l'unità sempre per la base, finché, sommato ad 1, non lo lascia invariato.

L'epsilon macchina dipende, ovviamente, dal computer e dal compilatore utilizzato; per scrivere un programma che ne determini il valore, è necessario servirsi di un ciclo (vedi capitolo 3).