Numeri interi in MATLAB

Nelle lezioni precedenti abbiamo visto come MATLAB usa i numeri floating point per rappresentare i numeri reali. In particolare, abbiamo osservato il tipo double e il tipo single. In più abbiamo introdotto i numeri complessi attraverso la funzione complex e come MATLAB utilizzi ben due numeri reali per rappresentare un numero complesso.

In questa lezione vedremo come in matlab si possono usare i numeri interi.

Numeri interi

Nella lezione in cui ho introdotto i tipi in virgola mobile abbiamo visto che, di default, MATLAB utilizza il double per rappresentare i numeri. Questo tipo utilizza 64 bit, o 8 byte, per rappresentare un numero reale. Nella stessa lezione ho poi parlato del tipo single che occupa la metà in termini di spazio e, tipicamente, richiede meno tempo per i calcoli. In poche parole, anche se il tipo di default è il double, quando non è richiesta la precisione offerta, è sufficiente utilizzare il single in maniera da ottimizzare le risorse impiegate nei nostri calcoli.

Analogamente, anche se il tipo di default è il double, se nei nostri calcoli si ha a che fare con numeri interi è possibile ottimizzare ancor di più il tempo e la memoria impiegati dai nostri calcoli utilizzando i tipi interi.

I tipi interi sono forniti da quasi tutti i linguaggi di programmazione e, a livello hardware, i calcoli che il processore effettua su di essi impiegano meno tempo. MATLAB offre ben otto tipi di interi diversi che sono riportati nella tabella seguente:

Tipo Intervallo di rappresentazione Descrizione
int8 \left[ -2^7, 2^7-1 \right] Interi con segno a 8 bit
int16 \left[ -2^{15}, 2^{15}-1 \right] Interi con segno a 16 bit
int32 \left[ -2^{31}, 2^{31}-1 \right] Interi con segno a 32 bit
int64 \left[ -2^{63}, 2^{63}-1 \right] Interi con segno a 64 bit
uint8 \left[ 0, 2^8-1 \right] Interi senza segno a 8 bit
uint16 \left[ 0, 2^{16}-1 \right] Interi senza segno a 16 bit
uint32 \left[ 0, 2^{32}-1 \right] Interi senza segno a 32 bit
uint64 \left[ 0, 2^{64}-1 \right] Interi senza segno a 64 bit
Tabella 1: Tipi interi in MATLAB

Dalla tabella precedente possiamo notare che i numeri interi possono essere divisi in due gruppi da 4, il primo composto dai numeri interi con segno e il secondo composto dai numeri interi senza segno. I tipi appartenenti al primo gruppo possono essere impiegati per rappresentare numeri negativi, mentre i secondi soltanto per rappresentare numeri positivi compreso lo zero. Il vantaggio di usare i numeri senza segno sta nel fatto che è possibile rappresentare numeri positivi più grandi rispetto agli interi con segno.

Il modo più semplice per ricordare a memoria il nome di questi tipi è quello di usare int, che sta per integer o intero, e porgli davanti una u se vogliamo rappresentare numeri senza segno (la u sta infatti per unsigned, i.e. senza segno) e porre come suffisso il numero di bit da usare: 8, 16, 32 o 64.

Creare una variabile o un valore intero è molto semplice. Basta utilizzare il nome del tipo e inserire tra parentesi il valore da rappresentare. Ad esempio, se vogliamo creare una variabile di tipo int8 con valore 15 basta inserire nel prompt il seguente comando:

>> x = int8(15)

x =

  int8

   15

Se poi usiamo il comando whos per avere informazioni sulla nostra variabile, il risultato sarà:

>> whos

  Name      Size            Bytes  Class    Attributes

  x         1x1                 1  int8               

Come si può vedere, la nostra variabile x occupa soltanto un byte, ossia 8 bit: otto volte meno rispetto ad un double.

In realtà, nel caso precedente, int8 rappresenta una funzione a tutti gli effetti. Essa prende in ingresso un valore, una variabile o un'espressione e la converte in un intero a 8 bit con segno. Ad esempio possiamo eseguire il seguente comando:

>> int16(5 + 3 * 2)

ans =

  int16

   11

Nell'esempio precedente, l'intera espressione 5 + 3 \cdot 2 viene trasformata in un intero a 16 bit.

Definizione

Usare i tipi interi

MATLAB, di default, memorizza i valori numerici usando il tipo double. Per poter utilizzare i tipi interi bisogna utilizzare esplicitamente le funzioni di conversione che hanno lo stesso nome del tipo che si vuole usare:

  • int8
  • int16
  • int32
  • int64
  • uint8
  • uint16
  • uint32
  • uint64

Queste funzioni prendono in ingresso un'espressione e la trasformano nel tipo desiderato.

Arrotondamento

Nell'invocare una delle otto funzioni di creazione interi, se il risultato dell'espressione passata come argomento è un numero con parte frazionaria, MATLAB provvede in automatico a convertire il valore nel più vicino intero. Ad esempio:

>> int16(3.6)

ans =

  int16

   4

>> int16(3.2)

ans =

  int16

   3

Nel primo caso, 3.6 viene convertito in 4 in quanto è l'intero più prossimo. Nel secondo caso, 3.2 viene convertito in 3. Caso particolare è, invece, quando la parte frazionaria è pari a 0.5. Prendiamo, ad esempio, il numero 3.5. In questo caso, sia 4 che 3 sono equidistanti dal valore in esame. Pertanto MATLAB sceglierà, tra i due, il numero intero che in valore assoluto è il più grande. Per cui:

>> int16(-3.5)

ans =

  int16

   -4

>> int16(3.5)

ans =

  int16

   4

Nel primo caso, infatti, per il valore -3.5, MATLAB potrebbe scegliere tra -4 e -3. Ma, se prendiamo il valore assoluto dei due numeri otteniamo, nel primo caso, \left|-4\right| = 4 mentre nel secondo caso \left|-3\right| = 3. Di conseguenza, MATLAB sceglierà -4. Viceversa, per il caso 3.5 MATLAB selezionerà 4.

Questo funzionamento, in gergo tecnico, prende il nome di arrotondamento o rounding ed è un tema abbastanza delicato che affronteremo nelle lezioni di calcolo numerico.

Nota

Arrotondamento di default

Se non specificato diversamente, MATLAB utilizza uno schema di arrotondamento di default.

In base a questo schema, un numero reale viene convertito nel numero intero più vicino. Nel caso in cui esistono due numeri interi ad uguale distanza, ossia quando la parte frazionaria è pari a 0.5, viene scelto il numero intero in valore assoluto più grande.

MATLAB permette di scegliere altri schemi di arrotondamento diversi da quello riportato sopra.

Funzioni intmax e intmin

Analogamente al caso dei numeri double per cui esistono le funzioni realmin e realmax, anche per i numeri interi esistono due funzioni che ci permettono di ricavare il minimo intero rappresentabile e il massimo intero rappresentabile: intmin e intmax.

Queste due funzioni richiedono in ingresso il nome del tipo racchiuso tra apici e restituiscono l'intero massimo o minimo rappresentabile per quel tipo.

Ad esempio, sappiamo che il tipo uint16 è in grado di rappresentare numeri interi compresi tra 0 e 2^{16} - 1 = 65535. Proviamo, quindi, ad introdurre i seguenti comandi nel prompt di MATLAB:

>> intmin('uint16')

ans =

  uint16

   0

>> intmax('uint16')

ans =

  uint16

   65535

Come si può osservare dall'esempio, i risultati combaciano con quanto ci aspettavamo.

Definizione

Minimo intero e Massimo intero rappresentabili

Le funzioni intmin e intmax permettono di ottenere, rispettivamente, il minimo intero e il massimo intero rappresentabile con un tipo.

Queste funzioni richiedono in ingresso il nome del tipo tra apici singoli e restituiscono il valore nel tipo richiesto

intmax('nome-tipo');
intmin('nome-tipo');

Operazioni sui numeri interi

Quando utilizziamo numeri interi, rispetto ai numeri in virgola mobile, bisogna fare attenzione alle operazioni matematiche che applichiamo ad essi.

L'insieme dei numeri naturali \mathbb{N} e quello dei numeri interi \mathbb{Z} sono chiusi rispetto alle operazioni di addizione e moltiplicazione. Ciò vuol dire che presi due numeri qualunque appartenenti a \mathbb{N} o \mathbb{Z}, la loro somma e moltiplicazione continua ad appartenere, rispettivamente, agli insiemi \mathbb{N} e \mathbb{Z}. I tipi interi di MATLAB, tuttavia, possono rappresentare un intervallo limitato di numeri naturali (gli unsigned) o interi, per cui può capitare che il risultato di una somma o moltiplicazione tra due interi sia al di fuori di tale intervallo. In questi casi, MATLAB semplicemente, senza segnalare alcun errore, darà come risultato il massimo o minimo intero rappresentabile. Prendiamo l'esempio seguente:

>> intmax('uint8')

ans =

  uint8

   255

>> x = uint8(240)

x =

  uint8

   240

>> x + 50

ans =

  uint8

   255

Come possiamo vedere, il massimo intero rappresentabile da un uint8 è 255. Nel momento in cui alla nostra variabile x, che vale 240, sommiamo 50, il risultato (che dovrebbe essere 290) collassa in 255.

Stesso discorso vale per la moltiplicazione:

>> intmax('uint16')

ans =

  uint16

   65535

>> x = uint16(4000)

x =

  uint16

   4000

>> x ^ 2

ans =

  uint16

   65535

In questo caso, la variabile x, che è un uint16 e vale 4000, viene elevata al quadrato. Il risultato di 4000^2, anzichè essere 16000000, collassa in 2^{16} - 1 = 65535.

Analogo discorso vale per la sottrazione, che è soddisfa la proprietà di chiusura per i numeri interi \mathbb{Z} ma non per quelli naturali \mathbb{N}:

>> intmin('int8')

ans =

  int8

   -128

>> x = int8(-100)

x =

  int8

   -100

>> x - 200

ans =

  int8

   -128

La divisione merita, invece, un discorso a parte. La divisione non soddisfa la proprietà di chiusura ne per i numeri interi ne per quelli naturali. In generale, quindi, il risultato di una divisione non necessariamente è un numero intero. In tal caso MATLAB restituirà l'arrotondamento del risultato, secondo le regole viste sopra:

>> x = int8(3)

x =

  int8

   3

>> x / 2

ans =

  int8

   2

Divisione per zero

Discorso a parte merita la divisione di un numero intero per zero in MATLAB. Infatti, un'operazione del genere porta ad un risultato inaspettato.

Come abbiamo visto, per i tipi floating point, double e single, una divisione per 0 restituisce il valore Inf che in MATLAB sta ad indicare una quantità talmente grande da non poter essere rappresentata.

Generalmente, la maggior parte dei linguaggi di programmazione trattano la divisione per zero come un'errore e forniscono dei meccanismi per segnalare questo evento. In MATLAB ciò non avviene, per cui, in coerenza con quanto avviene con i numeri in virgola mobile, una divisione per zero restituisce, nel caso degli interi, il massimo intero rappresentabile al posto di Inf.

Prendiamo l'esempio che segue:

>> x = int16(8);
>> y = int16(0);
>> x / y

ans =

  int16

   32767

Se x e y fossero stati due double il risultato sarebbe stato Inf che è, in un certo senso, un valore floating point che indica una quantità così grande da non poter essere rappresentata da un double. Nel caso degli interi, questo comportamento corrisponde al restituire il massimo numero intero rappresentabile, che in questo caso è 32767 per gli interi a 16 bit int16.

Del resto, se convertiamo Inf in un numero intero otteniamo il seguente risultato:

>> int16(Inf)

ans =

  int16

   32767
Nota

Divisione per zero

In MATLAB, se nel dividere due numeri interi si verifica una divisione per zero non verrà segnalato nessun errore. Il risultato sarà il massimo intero rappresentabile.

Questo comportamento è in coerenza con il fatto che la divisione per zero di un numero in virgola mobile restituisce Inf.

NaN e numeri interi

Così come per la divisione per zero, anche la conversione del valore NaN in un valore intero restituisce un risultato inaspettato.

Proviamo a vedere l'esempio che segue:

>> x = int16(0);
>> y = int16(0);
>> x / y

ans =

  int16

   0

Se x e y fossero stati due numeri in virgola mobile, il risultato sarebbe stato NaN, ossia un valore indefinito. Nel caso dei numeri interi, un risultato del genere non è rappresentabile.

Per questo motivo, i progettisti di MATLAB hanno deciso di trasformare NaN in zero quando si converte questo valore in un intero.

A prima vista, questa scelta può sembrare arbitraria. In realtà, la motivazione sta nel fatto che un valore indefinito può essere assimilato ad un valore logico falso. Nelle prossime lezioni vedremo le espressioni logiche in MATLAB, per il momento basti sapere che sono espressioni che hanno due possibili risultati: vero o falso. In generale in MATLAB qualsiasi valore diverso da 0 rappresenta il valore logico "vero", mentre lo zero rappresenta il "falso". Quindi, associare a NaN il valore 0 è una scelta di coerenza con un'espressione logica "falsa".

Questo comportamento può essere verificato con i comandi che seguono:

>> int16(nan)

ans =

  int16

   0
Nota

NaN e interi

In MATLAB, il valore indefinito NaN viene convertito in zero quando si applica la conversione a numeri interi.

Operazioni tra interi di tipo diverso

Sebbene MATLAB metta a disposizione otto tipi diversi per i numeri interi, non è possibile combinare nella stessa espressione interi di tipo differente. Ad esempio, un'espressione del genere produce un errore:

>> x = int8(8);
>> y = int16(3);
>> x * y
Error using  * 
Integers can only be combined with integers of the same class, or scalar doubles.

>> x + y
Error using  + 
Integers can only be combined with integers of the same class, or scalar doubles.

L'unico modo di effettuare queste operazioni è di convertire i valori nello stesso tipo intero. Ritornando all'esempio di prima si potrebbe risolvere il tutto in questo modo:

>> x = int8(8);
>> y = int16(3);
>> x_16 = int16(x);
>> x_16 * y

ans =

  int16

   24
Nota

Espressioni con interi di tipo diverso

In MATLAB non è possibile combinare nella stessa espressione valori di tipo intero diverso.

L'espressione deve contenere solo interi dello stesso tipo.

Operazioni tra numeri interi e numeri reali

MATLAB permette di inserire espressioni che coinvolgono valori di tipi diversi. Quindi è possibile combinare, con un'espressione, valori double, single e complex.

Analogamente, è possibile inserire nella stessa espressione valori in virgola mobile e valori interi. Il risultato in MATLAB è, tuttavia, differente rispetto a quanto accade in altri linguaggi di programmazione.

Ad esempio, in python, moltiplicando un numero in virgola mobile per un intero restituisce un numero in virgola mobile. Questo perché, nel caso in cui venisse restituito un intero si perderebbe precisione. Pensiamo a questo esempio:

3.3 \cdot 7 = 23.1

Se il risultato fosse convertito in un intero, otterremmo 23 perdendo di precisione.

MATLAB, tuttavia, adotta un approccio differente. Infatti, quando si presenta un'espressione che coinvolge sia interi che valori in virgola mobile, MATLAB esegue i seguenti passaggi:

  1. Tutti i valori non in virgola mobile vengono convertiti in double;
  2. Vengono effettuati i calcoli;
  3. Il risultato viene riconvertito, tramite arrotondamento, nel tipo intero utilizzato nell'espressione.

Ad esempio, inserendo i seguenti comandi:

>> x = 5.23;
>> y = int16(4);
>> x * y

ans =

  int16

   21

In questo esempio, il risultato corretto sarebbe: 5.23 \cdot 4 = 20.92. Applicando l'arrotondamento, MATLAB restituisce il valore intero 21.

Infine, espressioni che contengono numeri interi e numeri complessi non sono supportate.

Nota

Operazione tra numeri interi e valori in virgola mobile

In MATLAB, espressioni che contengono sia numeri interi che numeri in virgola mobile vengono gestite in questo modo:

  1. Tutti i valori sono convertiti in double;
  2. Vengono eseguite le operazioni;
  3. Il risultato è arrotondato e convertito in intero.

Operazioni tra complex e interi non sono supportate.

Quando usare i numeri interi

In questa lezione abbiamo visto le caratteristiche dei numeri interi. In particolare, abbiamo visto che per poterli usare è necessario specificare esplicitamente il tipo utilizzando le funzioni di conversione.

A questo punto sorge la domanda. Perché non utilizzare sempre i double, ossia il tipo predefinito di MATLAB, nei nostri calcoli?

Esistono due importanti motivazioni per voler usare gli interi.

Consiglio

I calcoli con i numeri interi sono più veloci

Tipicamente i calcoli che coinvolgono numeri interi sono eseguiti molto più velocemente dal processore rispetto ai calcoli in virgola mobile.

Per questo motivo, se abbiamo a che fare con quantità intere in cui non abbiamo bisogno della parte frazionaria, conviene usare i numeri interi per velocizzare l'esecuzione dei nostri calcoli.

La seconda motivazione è la seguente:

Nota

Un valore double non necessariamente è in grado di rappresentare un numero intero di grandi dimensioni con precisione assoluta

Un valore double è in grado di rappresentare fino ad un massimo di 15 cifre significative.

Per comprendere questa affermazione, partiamo dalla considerazione che una variabile uint64 è in grado di rappresentare con precisione assoluta valori giganteschi. Infatti, con un uint64 siamo in grado di rappresentare numeri interi compresi tra 0 e 2^{64} - 1 = 18446744073709551615.

Con una variabile double siamo in grado, in effetti, di rappresentare numeri fino a realmax('double') = 1.79769313486232e+308, ossia fino a circa 10^{308}, quindi valori molto più alti. Tuttavia, il compromesso è che perdiamo in precisione a causa del troncamento. Infatti, un double riesce a rappresentare fino a circa 15 o 16 cifre significative.

Effettuando una conversione di intmax('uint16') in double il risultato che otteniamo è il seguente:

>> intmax('uint64')

ans =

  uint64

   18446744073709551615

>> double(ans)

ans =

      1.84467440737096e+19

Come si può osservare, le ultime cinque cifre del numero intero sono arrotondate. Per cui otteniamo che:

18446744073709{\color{blue}{551615}} \quad \rightarrow \quad 18446744073709{\color{red}{600000}}

Quindi, quando i valori reali non sono necessari, è spesso conveniente usare i numeri interi.

In sintesi

In questa lezione abbiamo studiato i numeri interi. Abbiamo visto come definire un numero intero, come usarli in espressioni complesse.

MATLAB tratta i numeri interi in maniera differente ad altri linguaggi. Nella divisione per zero non segnala errori e quando vengono sforati i limiti di rappresentazione, i valori collassano nel massimo o minimo numero intero rappresentabile.

Inoltre, MATLAB, quando si hanno espressioni che combinano numeri interi e valori in virgola mobile restituisce sempre un valore intero.