Alcune note sui tipi discreti del C++

con riferimento al tipo intero

Siete il visitatore n.

Click here for English version

Pagina ottimizzata per risoluzione 1024x768

     
 
Torna alla Home Page

Recapiti

Argomenti simili su questo sito

Funzioni C++ per input da tastiera controllato in applicazioni console

 

In questa pagina sono descritti alcuni problemi che possono sorgere utilizzando i tipi discreti del C++. In particolare sono riportate alcune note ed esempi che possono aiutare ad evitare, in fase di programmazione, errori logici nell'utilizzo degli interi.



I tipi discreti nel C++ : il tipo int

Il C++ consente l'impiego di alcuni tipi predefiniti, quali il tipo int. Si tratta, come il suo stesso nome suggerisce, di un tipo in grado di rappresentare gli interi, o, meglio, un sottoinsieme degli interi. In generale, è possibile utilizzare più di un tipo di interi, per esempio short int, int, long int, ciascuno con la propria dimensione (occupazione di memoria), ma, attenzione, non è sempre vero che short int, int e long int hanno tre dimensioni diverse.
Gli interi rispettivamente maggiore e minore che possono essere rappresentati all'interno di una macchina, diciamo di tipo int, dipendono sia dalla forma nella quale i numeri stessi sono rappresentati, sia, soprattutto, dalla quantità di memoria utilizzata per memorizzarli. Lo standard C++ non dice quanta memoria deve essere utilizzata per il tipo int, dice quanta memoria deve essere almeno utilizzata per il tipo int. Per esempio, il tipo int è generalmente memorizzato su quattro byte, ma questo non è sempre vero, in alcune implementazioni esso è memorizzato su due byte. Ciò, naturalmente, contrasta con la portabilità del codice. Riguardo agli interi, nel momento in cui vi accingete a scrivere un programma, dovreste per prima cosa stabilire i limiti ammessi o necessari per i vostri interi, e quindi la minima dimensione di memoria per essi richiesta; in seguito, potete scegliere il tipo di cui avete bisogno, ad esempio int o short int, una volta che conoscete i suoi limiti sul vostro sistema. Per ricompilare il vostro codice con un diverso compilatore e/o su una piattaforma diversa, e ottenere un codice che funzioni (almeno, riguardo al tipo intero), dovreste aver cura di accertarvi che il nuovo compilatore usi per i vostri tipi interi almeno lo stessa dimensione del compilatore originale. Supponete di scrivere un programma che utilizza variabili di tipo int memorizzate dal compilatore su quattro byte (e, di conseguenza, tutte le operazioni relative al tipo int saranno tradotte in operazioni che coinvolgono quattro byte dati); in tal modo, potete gestire numeri come 100000, ad esempio; supponete poi di compilare il vostro codice con un diverso compilatore, che memorizza gli interi su due byte: le conseguenze non si possono immaginare. Numeri quali 100000 sarebbero troncati, o sarebbe loro assegnato un altro valore, questo potrebbe dipendere dal compilatore, comunque tutt'altro che 100000. Degno di menzione: una volta è accaduto che, a causa di un errore in qualche modo simile a questo, un razzo nel quale era stato fatto un cambiamento hardware, senza però modificare adeguatamente il software, dopo il lancio andò fuori controllo e dovette essere distrutto. Non permettete mai qualcosa del genere!!

Ma c'è anche qualcos'altro da dire: in generale, altri compilatori dovrebbero usare lo stesso spazio utilizzato dal compilatore originale, altrimenti potrebbero verificarsi alcuni effetti imprevisti, specialmente nel caso eseguiate operazioni bit a bit sui vostri interi.

Attenzioni alle dimensioni e all'aritmetica

I brevi programmi d esempio in questa pagina sono stati scritti e compilati con DevCpp 4.9.8.0 per Win. Si invita a leggere la sezione "Riconoscimenti e avvertenze".

Per controllare la dimensione (in byte) con la quale il compilatore memorizza e tratta gli elementi di un dato tipo, potete usere nei vostri programmi l'operatore sizeof (type). Questo operatoer è valutatao a tempo di compilazione, non di esecuzione (run-time). Se desiderate scrivere un programma in cui, per esempio, utilizzati interi da memorizzare su due byte, potete eseguire questo controllo attraverso l'operatore sizeof (int). Siupponiamo che questo operatore restituisca 2, così il tipo int va bene. Se, su un'altra piattaforma, compilate il programma e, lì, sizeof(int) restituisce un valore maggiore di 2, il programma potrebbe non funzionare; se, invece, scrivete un programma che utilizza interi memorizzati su quattro byte, e poi, cambiando piattaforma, il programma ricompilato vi informa attraverso l'operatore sizeof che il compilatore memorizza il tipo int su due byte, mai, dico mai, dovreste usare il vostro programma originale su tale piattaforma.

Una possibilità di consentire la portabilità del codice consiste nell'utilizzare interi exact-type, per esempio int16_t, int32_t, int64_t, per i quali è necessaria la libreria stdint. Controllate come utilizzarla.

Un modo per controllare che il vostro compilatore memorizza gli interi sul numero di byte che vi aspettate è, per esempio, quello di usare nelle prime righe del vostro programma qualcosa come

if (sizeof (int) != 4)

{cout << "Int type not stored on four bytes; program aborted\n";

exit (1);}

Riguardo alle dimensioni dei tipi interi, per esempio, sul mio sistema DevCpp per Win si comporta in questo modo:

sizeof (short) = 2
sizeof (short int) = 2
sizeof (int) = 4
sizeof (long int) = 4
sizeof (long long) = 8
sizeof (long long int) = 8

Negli esempi seguenti, salvo diversa indicazione, assumeremo che il tipo int è memorizzato dal compilatore su quattro byte. Concetti analoghi, mutatis mutandis, possono essere applicati a interi su meno o più di quattro byte (generalmente due e otto). Talvolta, negli esempi, useremo int su due byte, visto che i loro limiti sono abbastanza piccoli e facili da ricordare.

Con il tipo int su due byte, e la rappresentazione in complemento a due, i numeri int minore e maggiore che possono essere rappresentati sono -32768 and +32767.

Con la rappresentazione in complemento a due su N bit, ad esempio 16, i numeri interi minore e maggiore che possono essere rappresentati sono -(2^(N-1)) e 2^(N-1)-1. Si fa notare che in queste righe relative alla rappresentazione dei numeri il simbolo ^ è usato per significare "elevato a", e non è l'operatore C++ XOR. Dato un numero intero x, la sua rappresentazione X in complemento a due è

X=x se 0 <= x <= 2^(N-1) -1 (ad esempio, 0<= x <= 32767)

X= 2^N - |x|, se -(2^(N-1)) <= x <= -1 (ad esempio, -32768 <= x <= -1)

Viceversa, data la rappresentazione X in complemento a due su N bit di un intero, il corrispondente numero intero può essere determinato dalle relazioni

x=X se 0 <= X <= 2^(N -1) -1 (ad esempio, 0<= X <= 32767)

x= - (2^N - X) , se 2^(N-1) <= X <= (2^N) -1 (ad esempio, 32768 <= X <= 65535)

Ad esempio, x= -32768 è rappresentato come X=32768, x= -32769 non può essere rappresentato su 16 bit, x= -32767 è memorizzato come X=32769, x= -1 come X=65535, mentre x=0 diventa X=0, x=1 diventa X=1, x=32767 diventa X=32767.

Viceversa, X=65534, essendo maggiore di 2^(16-1)-1=32767, rappresenta un numero negativo, esattamente -(2^16-X) = -(65536-65534) = -2.

Dopo quanto descritto qui sopra, potete vedere che nella rappresentazione in complemento a due, per lo più o universalmente adottata per rappresentare i numeri interi all'interno delle macchine, se il primo bit (MSb, il bit più significativo) è zero il numero rappresentato è positivo o è zero, se il MSb è uno il numero rappresentato è negativo.

Diamo ora un'occhiata ad alcuni problemi che possono sorgere con gli interi, se non si fa abbastanza attenzione.

Incremento/decremento, adddizione/sottrazione

Supponete di voler eseguire una certa operazione per 40000 volte, supponete di scrivere il seguente programma C++. Se siete consapevoli di aver bisogno di un intero su quattro byte, ma non controllate se il tipo int è davvero memorizzato su quattro byte, e il vostro compilatore memorizza gli interi su due byte, allora il programma

int a=0;

for (int a=0; a<40000; a++)

{// operations to be done}

esegue un loop infinito, poiché dopo 32767 viene ... -32768, -32767 ...

E' possibile che il compilatore vi avverta, se nel programma viene assegnato il valore costante 40000 a una variabile che non può coontenerlo.

In questo caso dovete aver cura di utilizzare un tipo intero memorizzato almeno su 4 byte, ad esempio potreste provare con il tipo long long int. Ricordate che questo era un esempio; come altro esempio, nel DevCpp 4.9.8.0 per Win un int è su 4 byte e un long long int è su 8 byte.

Attenzione: in generale il tipo long int è memorizzato o su un numero di byte pari al tipo int, o su più byte: controllate la vostra implementazione.

Anche se avete avuto cura che i valori delle variabili possano essere contenuti nel tipo che avete scelto per esse, la loro somma o sottrazione potrebbe generare un risultato fuori dai limiti ammessi per tale tipo, e voi non potreste accorgervene a tempo di esecuzione (a meno che proviate a gestire l'overflow, il carry e così via). Forse potrebbe essere utile utilizzare tipi più "grandi" per memorizzare il risultato di tali operazioni.

Cosa analoga (molto più frequente) quando moltiplicate due interi; se siete sicuri che ogni possibile risultato possa essere memorizzato in un int, potete usare un int per il risultato, altrimenti dovete utilizzare un tipo più "grande". Con un tipo int su 4 byte, per il quale i limiti sono -2147483648 e +2147483647, scriviamo il seguente programma:

int a= 200000; int b=200000;
cout << a*b << endl;
int c=2000000000;

if (a*b < c)
{cout << "success\n";}
else
{cout << "failure\n";}

Quale pensate sia il risultato? Il programma visualizza sullo schermo "success", e a*b viene mostrato valere 1345294336 dall'operatore cout, invece di 40000000000.

Avreste dovuto usare un long long int per c e assegnare a*b a un long long int d, ammesso che il tipo long long int sia su otto byte.

Valore assoluto

Quando utilizzate la funzione int abs(int n), data la asimmetria dell'intervallo di rappresentazione degli interi in complemnto a due, dovreste fare attenzione che n non sia il minimo intero ammesso; infatti, supposto che uno short int sia su due byte, il seguente programma non funziona.

short int asi = 0;
asi = -32768;
short int aaa=abs(asi);
cout << "asi = " << asi << " , aaa = abs(asi) = " << aaa << endl;

Poiché abs(-32768) non può essere rappresentato su due byte, la funzione abs restituisce -32768, così sullo schermo si ottiene

asi = -32768 , aaa = abs(asi) = -32768

Dividere un numero in cifre

In base all'implementazione, il quoziente della divisione intera, quando il quoziente è negativo, può essere troncato verso zero o verso meno infinito. Nel primo caso, il resto ha lo stesso segno del dividendo, nel secondo ha il segno del divisore. In ogni caso,

a = (a/b)*b + a%b

Supponete di voler dividere un numero intero in cifre, memorizzandole in un array. Il seguente programma, se a < 0, può non funzionare;

const int n_max_int_digits=32; // int type on 4 bytes
int digits[n_max_int_digits]
int serv = 0;
// ...
// serv is assigned a negative value
//...
// dividing serv into digits
for (int i=0; i<n_max_int_digits; i++)
{
digits[i] = abs (serv%10) ;
serv = serv/10;
}

Infattim suoponiamo che serv sia -342; se serv/10 vale -34, e perciò serv %10 vale -2, allora digits[0]=2, ma se serv/10 = -35, serv%10 = +8, e a digits[0] viene assegnato un valore errato.

Per dividere in cifre un numero intero, suggerisco invece la seguente routine:

int servizio=0;

bool valore_max_is_positivo=false; // valore_max_is_positivo == true if valore_max >= 0
if (valore_max >=0)
{valore_max_is_positivo=true;}

bool valore_min_is_positivo=false; // valore_min_is_positivo == true if valore_min >= 0
if (valore_min >=0)
{valore_min_is_positivo=true;}

// dividing valore_min into digits
if (valore_min_is_positivo == true)
{
servizio=valore_min;
for (int i=0; i<n_max_cifre_tipo;i++)
{
cifre_valore_min[i]=servizio%10;
servizio=servizio/10;
}
}

if (valore_min_is_positivo == false)
{
servizio=valore_min;
for (int i=0; i<n_max_cifre_tipo;i++)
{
int j=0;
while (servizio%10!=0)
{
servizio++;
j++;
}
cifre_valore_min[i]=j;
servizio=servizio/10;
}
}

Sperando di esservi stato utile, spero di poter aggiungere presto qualcos'altro.

Se volete, scrivetemi per commenti e suggerimenti.

Buon lavoro!

Riconoscimenti e avvertenze

I brevi programma di esempio riportati in questa pagina sono stati scritti e compilati utilizzando l'ambiente di sviluppo Dev C++ 4.9.8.0 per Win. Si tratta di un software scaricabile gratuitamente, soggetto comunque a diritti e copyright di vario genere (Copyright Bloodsheed Software, licenza GNU, ecc.). In queste righe e in quelle precedenti, la citazione dell'opera di terze parti e delle licenza richieste è incompleta e ha carattere indicativo; in ogni caso, in questa pagina non viene riportato alcun programma scritto da altri. Per utilizzare compilatori o altri programmi, leggerne attentamente le condizioni di utilizzo.

Trattandosi di materiale messo gratuitamente a disposizione di chiunque lo desideri, ne è vietata ogni forma di utilizzazione a fini commerciali. Ogni forma di utilizzazione per uso personale è certamente permessa.

Se in qualche modo si intende utilizzare per usi non puramente personali il contenuto di questa pagina, è fatto obbligo di richiedere un permesso scritto all'autore di questo articolo (anche via e-mail).

Le informazioni contenute in questa pagina sono fornite così come sono, senza alcuna forma di garanzia. Non mi assumo naturalmente alcuna responsabilità per danni diretti o indiretti a persone o a cose derivanti dalle informazioni contenute in questo articolo.

 

 

 

 
 
 

Se volete, inviatemi i vostri commenti (anche in forma anonima)

   
Il vostro Paese
I vostri commenti

Se desiderate una risposta indicate qui il vostro indirizzo

   
 
 
 
 
 

Se volete contattarmi per chiarimenti o suggerimenti potete farlo al seguente indirizzo:

f_iacopetti@libero.it

 
Ultimo aggiornamento: 30 Luglio 2004