Tipi Numerici Interi in C#

Introduciamo, in questa lezione, il primo tipo di dato numerico in C#: gli interi.

Gli interi sono uno dei tipi di dati più comuni e basilari in qualsiasi linguaggio di programmazione. In C#, come in molti altri linguaggi, gli interi sono utilizzati per memorizzare numeri interi, ossia numeri senza parte decimale.

Vedremo come dichiarare e utilizzare variabili di tipo intero e quali sono i tipi interi messi a disposizione dal linguaggio.

Interi

Uno dei tipi interi più semplici che il linguaggio C# mette a disposizione sono gli interi o int.

Un intero, come dice il nome, rappresenta un numero intero, ossia senza parte decimale. Gli interi possono essere positivi o negativi, quindi hanno un segno. In effetti, non dovrebbe sorprendere il fatto che il tipo int sia uno dei tipi fondamentali dato che un elaboratore lavora direttamente con numeri.

Per dichiarare una variabile di tipo int è sufficiente scrivere:

int numero;

A questo punto possiamo assegnare valori numerici alla variabile numero:

numero = 42;

In questo caso abbiamo usato un valore letterale, o semplicemente letterale, e lo abbiamo assegnato alla variabile. Un valore letterale è un valore che viene scritto direttamente nel codice sorgente. In questo caso il valore letterale è 42.

A questo punto, è importante riprendere il concetto di tipo. Nell'esempio di sopra abbiamo dichiarato una variabile di tipo int. Questo significa che la variabile numero può contenere solo valori interi. Non possiamo assegnare alla variabile un valore testuale.

Prendiamo l'esempio seguente:

int a = 42;
int b;

/* Possiamo assegnare a b il valore di a */
b = a;

In questo caso, la variabile b conterrà il valore 42 ed il codice è valido in quanto a e b sono entrambe variabili di tipo int.

Il codice che segue contiene, invece, valori non validi:

string a = "nome";
int b;

/* Questo codice non è valido */
b = a;

In questo caso, la variabile a è di tipo string e non può essere assegnata ad una variabile di tipo int. Il codice non compila.

Risulta fondamentale, quindi, ricordarsi che il tipo di una variabile determina i valori che essa può contenere e le operazioni che possiamo effettuare su di essa.

Definizione

Tipo int

Una variabile di tipo int è in grado di memorizzare numeri interi senza parte frazionaria.

La sintassi per dichiarare una variabile di tipo int è:

int nome_variabile;

Rappresentazione binaria dei numeri interi

Internamente, un dispositivo di elaborazione rappresenta i numeri in formato binario. Questo significa che un numero intero viene rappresentato come una sequenza di bit. Ad esempio, il numero 42 in binario è 101010.

Non è fondamentale, a questo punto, comprendere come funziona questa rappresentazione. Quello che bisogna tenere presente è che il numero di bit con cui si rappresenta un intero, così come qualsiasi altro dato, è limitato.

In particolare, un numero intero int utilizza 4 byte o 32 bit. Tuttavia, uno di questi bit viene utilizzato per rappresentare il segno, per cui un numero intero utilizza 31 bit. La conseguenza naturale è che con un tipo int non è possibile rappresentare tutti i numeri interi ma soltanto un intervallo limitato di essi.

Detto questo, un intero int è in grado di rappresentare un numero intero compreso tra 2^{31} - 1, ossia 2147483647, e -2^{31}, ossia 2147483648.

Definizione

Intervallo di numeri interi rappresentabili con un int

In linguaggio C#, una variabile di tipo intero int utilizza 4 byte per memorizzare numeri interi. Tali numeri possono essere compresi nell'intervallo:

-2^{31} = -2147483648 \leq x \leq 2^{31} - 1 = 2147483647

Un intervallo così ampio di numeri interi è sufficiente per la stragrande maggioranza dei programmi. Esistono casi in cui, invece, risulta necessario rappresentare numeri più grandi. Oppure è necessario risparmiare memoria ed usare meno di 4 byte. Per tal motivo, il linguaggio C# mette a disposizione altri tipi di interi con caratteristiche differenti.

Vediamo quali sono.

Tipi di interi

Il linguaggio C# mette a disposizione 8 diversi tipi di interi. Collettivamente, questi tipi prendono il nome di tipi interi.

Abbiamo visto già il tipo int che rappresenta i numeri interi, approssimativamente, tra -2 miliardi e 2 miliardi ed impiega 4 byte.

Esistono i tipi short (abbreviazione di short integer o intero corto) e long (abbreviazione di long integer o intero lungo). Questi due tipi sono funzionalmente identici al caso int; quello che cambia è la quantità di byte adoperati per memorizzare numeri. Il tipo short utilizza 2 byte ed è in grado di rappresentare numeri compresi, approssimativamente, tra -32000 e 32000. Il tipo long, viceversa, adopera ben 8 byte ed è in grado di memorizzare numeri di dimensione molto più elevata: numeri approssimativamente compresi tra -9 e 9 trilioni!

I tipi interi visti sinora, int, short e long, sono tutti tipi con segno. In altre parole, sacrificano uno dei bit per usarlo come segno. Facendo ciò, sono in grado di memorizzare sia numeri positivi che negativi.

Possono presentarsi situazioni in cui non sia necessario utilizzare numeri negativi. In tal caso, si può rinunciare al bit del segno per estendere l'insieme dei numeri rappresentabili. In tal caso si parla di tipi interi unsigned cioè senza segno. Come esistono tre tipi con segno, esistono i complementari senza segno e sono: ushort, uint e ulong. Il prefisso u davanti ad essi sta ad indicare il fatto che sono interi senza il segno. Il numero di byte che utilizzano per rappresentare gli interi è analogo alla loro controparte con segno, per cui: un ushort usa 2 byte, un uint usa 4 byte mentre un ulong usa 8 byte.

Questi tipi senza segno sono in grado di memorizzare numeri compresi tra 0 e 65000 per gli ushort, tra 0 e 4 miliardi per gli uint e tra 0 e 18 trilioni per gli ulong.

Esistono due ultimi tipi interi che, tuttavia, sono utilizzati per scopi differenti. Il primo è il byte che, come dice il nome, utilizza solo 8 bit, ossia un singolo byte, per rappresentare numeri che vanno da 0 a 255 (per cui è senza segno). Sebbene formalmente esso rappresenti un intero, il tipo byte viene adoperato per memorizzare sequenze di byte qualunque in memoria. In altre parole, raramente viene utilizzato per rappresentare numeri.

Infine, esiste il tipo sbyte, ossia la controparte con segno di byte. Anch'esso adopera 8 bit, un byte, per rappresentare numeri con segno che vanno da -128 a +127. Sebbene raramente adoperato, il tipo sbyte completa l'insieme degli interi.

Ricapitolando, la tabella che segue mostra gli 8 tipi di interi messi a disposizione dal linguaggio C# e le loro proprietà:

Nome Numero di Byte Con Segno Valore Minimo Valore Massimo
byte 1 No 0 255
sbyte 1 Si -128 127
short 2 Si -32 768 32 767
ushort 2 No 0 65 536
int 4 Si -2 147 483 648 2 147 483 647
uint 4 No 0 4 294 967 295
long 8 Si -9 223 372 036 854 775 808 9 223 372 036 854 775 807
ulong 8 No 0 18 446 744 073 709 551 615
Tabella 1: Tipi interi in C# e intervallo di rappresentazione

Letterali interi

Abbiamo visto i vari tipi interi che il linguaggio C# mette a disposizione. Per poter utilizzare una variabile di uno di questi tipi, è necessario dichiararla facendola precedere dal tipo prescelto.

Ad esempio, per dichiarare una variabile di tipo long è sufficiente scrivere:

long x;

Ora, il problema da affrontare riguarda i valori letterali che possiamo assegnare. Nel codice, infatti, quando si scrive un numero intero, il compilatore, tipicamente, lo interpreta come int, quindi con segno.

Tuttavia, se scriviamo una riga di codice di questo tipo:

ushort valore = 34;

il compilatore è abbastanza intelligente da convertire il valore in uno ushort privo di segno.

Analogamente, se inseriamo un numero di una grandezza superiore a quella consentita da un int, il compilatore comprende che si tratta di un letterale long. Ad esempio, il seguente codice è corretto:

long valore = 10000000000;

In questo caso, il numero letterale pari a 10 miliardi, viene automaticamente convertito in un long. Del resto non potrebbe essere memorizzato in un int.

A volte è necessario forzare il compilatore a considerare un numero come un tipo intero diverso da int. Per fare ciò, è sufficiente aggiungere un suffisso al numero. I suffissi che possiamo utilizzare sono:

  • u o U per uint o ulong;
  • l o L per long;
  • ul o UL per ulong.

Ad esempio, nel seguente codice usiamo dei differenti suffissi per forzare il compilatore a considerare i numeri come uint, ulong e long:

uint valore = 100000u;
ulong valore2 = 10000000000ul;
long valore3 = 10000000000L;
Definizione

Letterali interi in C#

I letterali interi sono valori numerici che vengono scritti direttamente nel codice sorgente.

In C# un numero intero viene considerato di tipo int a meno che non sia specificato diversamente. Per forzare il compilatore a considerare un numero come un tipo intero diverso da int, è possibile aggiungere un suffisso al numero:

  • u o U per uint o ulong;
  • l o L per long;
  • ul o UL per ulong.
Nota

Attenzione ai suffissi per i letterali interi

I suffissi per i letterali interi possono essere sia scritti in minuscolo che in maiuscolo:

  • u o U;
  • l o L;
  • ul o UL.

Il nostro consiglio è quello di scriverli sempre in maiuscolo per evitare che la l minuscola possa essere confusa con un 1 (uno) o con una i maiuscola.

Raggruppamento di Cifre

A partire da C# 7.0 è possibile utilizzare un raggruppatore di cifre, ossia un carattere speciale, per rendere più leggibile un numero intero.

Tipicamente, quando si scrivono numeri di grandi dimensioni, si usa raggruppare le cifre a gruppi di 3 per facilitarne la lettura.

In Italia, ad esempio, si adopera il punto come raggruppatore di cifre e la virgola per separare la parte decimale. Per cui, il numero 1 miliardo e mezzo si scrive come 1.500.000.000.

Nei paesi anglosassoni, come gli Stati Uniti, si adopera la virgola per separare le cifre ed il punto come separatore decimale. Per cui il numero un miliardo e mezzo diventa: 1,500,000,000.

In C# si può adoperare il carattere _ (underscore) per separare le cifre. Ad esempio, il numero 1 miliardo e mezzo si scrive come 1_500_000_000:

int miliardo_e_mezzo = 1_500_000_000;

La convenzione è quella di adoperare il separatore per raggruppare le cifre a tre a tre ed evidenziare, quindi, le migliaia, i milioni, i miliardi e così via.

Inoltre, in C# non importa dove si inserisce il separatore. Infatti possiamo scrivere il numero di sopra nei modi seguenti che sono tutti validi:

int miliardo_e_mezzo = 1_500_000_000;
int miliardo_e_mezzo = 15_00_000_000;
int miliardo_e_mezzo = 1_500__000___000;
Definizione

Raggruppamento di Cifre in C#

A partire da C# 7.0 è possibile utilizzare il carattere _ per separare le cifre di un numero intero.

Il separatore _ può essere inserito ovunque all'interno del numero:

int numero = 1_500_000_000;

Valori Esadecimali e Binari

Per quanto riguarda i valori letterali interi, oltre ad usare il sistema decimale, è possibile in C# utilizzare i sistemi esadecimale e binario.

Per scrivere un valore letterale binario, ossia in base 2, è sufficiente far precedere il numero dal prefisso 0b o 0B ed utilizzare soltanto i numeri 0 e 1. Ad esempio, il numero binario 1010 si scrive come 0b1010:

int binario = 0b1010;

Analogamente, per scrivere un valore letterale esadecimale, ossia in base 16, è sufficiente far precedere il numero dal prefisso 0x o 0X e utilizzare i numeri da 0 a 9 e le lettere da A a F. Ad esempio, il numero esadecimale FF si scrive come 0xFF:

int esadecimale = 0xFF;
Definizione

Valori Esadecimali e Binari in C#

In C# è possibile scrivere valori letterali interi in base 2 e in base 16.

La sintassi per scrivere un valore binario è:

int binario = 0b1010;

La sintassi per scrivere un valore esadecimale è:

int esadecimale = 0xFF;

Scelta del tipo di intero

Concludiamo questa lezione introduttiva sui tipi interi con una considerazione pratica: come scegliere il tipo di intero da utilizzare?

Avendo a disposizione ben 8 tipi interi la scelta dipende molto dal contesto. Da un lato, potremmo utilizzare come criterio di scelta la grandezza del numero da memorizzare. Se sappiamo i limiti entro cui la nostra variabile può variare, possiamo scegliere il tipo di intero più piccolo in grado di contenerla. In altre parole, un criterio possibile è quello di risparmio della memoria.

Ad esempio, volendo memorizzare l'età di una persona possiamo considerare il fatto che tale valore non potrà mai essere negativo e difficilmente supererà i 120 anni. In tal caso, possiamo utilizzare un byte che occupa solo 1 byte ed è in grado di memorizzare valori da 0 a 255.

Un altro esempio potrebbe essere il voler memorizzare il saldo di un conto corrente di una persona. In tal caso il saldo potrebbe essere negativo e positivo e potrebbe variare di molto. In ogni caso, è difficile che il conto di una persona superi i 2 miliardi di euro. In tal caso, possiamo utilizzare un int che occupa 4 byte.

Questo criterio di scelta ha i suoi meriti ma ha anche i suoi svantaggi. In primo luogo, oggigiorno la dimensione della memoria di un elaboratore è tale che non è più necessario risparmiare pochi byte. In secondo luogo, utilizzare tipi più piccoli potrebbe comportare un overhead in termini di prestazioni. Quest'ultima considerazione potrebbe essere contraddittoria. In realtà, il processore tipicamente lavora con numeri di grandezza maggiore. Ad esempio, processori a 64 bit sono ottimizzati per lavorare con interi di 8 byte. Per cui, per poter lavorare con un byte, il numero dovrà prima essere convertito in un long e poi essere processato. Questo comporta un aumento del tempo di esecuzione.

Esistono, invece, altri casi in cui bisogna lavorare direttamente con l'hardware o con la memoria. In tal caso, risulta fondamentale scegliere il tipo di intero più adatto. Ad esempio, dovendo processare dati raw o binari, è necessario utilizzare tipi di interi di dimensioni precise.

Ricapitolando:

Consiglio

Come scegliere il tipo di intero

Il consiglio che possiamo dare nella scelta del tipo di intero è quello di utilizzare il tipo int e long per la maggior parte dei casi. Questi tipi sono ottimizzati per i processori moderni e sono in grado di memorizzare un'ampia gamma di numeri.

Nel caso in cui sia necessario, se ne possono usare le versioni senza il segno uint e ulong per memorizzare soli numeri positivi.

In casi particolari, quando vi è un chiaro beneficio nell'utilizzare tipi più piccoli, si possono utilizzare short e byte.

In Sintesi

In questa lezione sulle variabili intere in C# abbiamo visto che:

  • In C# esistono 8 tipi interi: byte, sbyte, short, ushort, int, uint, long e ulong.
  • I tipi interi con segno possono memorizzare numeri positivi e negativi.
  • I tipi interi senza segno memorizzano solo numeri positivi.
  • I tipi interi utilizzano un numero fisso di byte per memorizzare i numeri.
  • I numeri interi possono essere scritti in base 2 e in base 16.
  • È possibile utilizzare un separatore _ per rendere più leggibile un numero intero.
  • La scelta del tipo di intero dipende dal contesto e dal range di valori che si vuole memorizzare.

Nella prossima lezione introdurremo i tipi numerici in virgola mobile.