La promozione aritmetica del tipo è una conversione adottata dal compilatore Java nell'ambito di espressioni numeriche che coinvolgono tipi di dato differenti. Il compilatore necessita di effettuare delle adeguate conversioni sui tipi coinvolti in modo tale che l'espressione matematica abbia un tipo corretto per il risultato.
Compilatore e conversioni di tipo
Vediamo prima di tutto come il compilatore effettua le conversioni di tipo in casi molto semplici. In Java abbiamo a disposizione i seguenti tipi primitivi numerici: byte
(8 bit), short
(16 bit), int
(32 bit), long
(64 bit), float
(32 bit) e double
(64 bit).
Per comprendere come agisce il compilatore è fondamentale avere in mente le semplici regole di conversione implicita del tipo sulle tipologie numeriche appena elencate (Widening conversion):
- una variabile di tipo
byte
può essere assegnata ad una di tiposhort
,int
,long
,float
odouble
; - il valore contenuto nella variabile di tipo
byte
viene automaticamente convertito in un valore di tiposhort
,int
,long
,float
odouble
; - una variabile di tipo
short
può essere assegnata ad una variabile di tipoint
,long
,float
odouble
; - il valore contenuto nella variabile di tipo
short
viene automaticamente convertito in un valore di tipoint
,long
,float
odouble
.
Applicando le stesse considerazioni e, seguendo l'ordine che abbiamo definito da short
a double
, deduciamo che un int
può essere assegnato ad un long
, float
o double
, un long
ad un float
o double
ed un float
soltanto ad un double
.
Ovviamente ogni valore di tipo A può essere assegnato ad una variabile dello stesso tipo A. Queste conversioni di tipo sono automatiche perchè il valore di partenza appartiene ad un range numerico di cardinalità inferiore rispetto al range numerico del tipo di destinazione.
Questo aspetto è comprensibile notando il numero di bit utilizzati per la rappresentazione numerica di ciascun tipo. Sulla base di quanto appena detto mostriamo alcuni esempi in cui il compilatore effettua conversioni numeriche implicite:
byte valByte = 50;
short valShort = valByte;
int valInt = valShort;
long valLong = valInt;
float valFloat = valLong;
double valDouble = valFloat;
Tipi di dato e conversioni tra tipi
Cosa accade se proviamo ad assegnare un valore di una variabile di un tipo più grande ad una variabile di un tipo più piccolo? La risposta è un errore di compilazione. Il compilatore ci avverte dell'impossibilà di effettuare la conversione automatica in quanto il valore che si intende assegnare alla variabile di destinazione semplicemente potrebbe non essere contenuto nel suo range del tipo.
Pensiamo a un valore int
che cerchiamo di assegnare ad una variabile di tipo byte
. Il range del tipo int
è -2.147.483.648,+2.147.483.647 mentre quello dei byte -128,+127, il compilatore considera il tipo della variabile e ci avverte dell'impossibilità di effettuare la conversione in quanto il range int
non è contenuto nel range short
. Il fatto che il compilatore consideri solo il tipo porta ad avere per l'istruzione
valByte = valInt;
un errore di compilazione anche se il valore contenuto nella variabile int
è presente nel range dei valori di tipo byte
.
Un aspetto interessante è un'eccezione a questo comportamento attuata dal compilatore nell'ambito di assegnazioni di valori numerici diretti ad una variabile (literals). In queste situazioni il compilatore rilassa le sue regole ed effettua una conversione del tipo se il valore, come nell'esempio precedente, è contenuto nel range del tipo di destinazione.
Questo è ciò che accade con la prima istruzione di assegnazione del valore 50 ad un tipo byte. Per comprendere questo aspetto occorre sapere che le costanti numeriche intere vengono automaticamente considerate come tipo int
, mentre quelle in virgola mobile come double
. Cosi l'istruzione
byte valByte = 50;
significa assegnare un int
ad un byte
. Il valore assegnato è un literal, il compilatore rilassa il suo comportamento osservando che il valore appartiene al range del tipo byte
, ed effettua la conversione di tipo.
Esistono delle situazioni in cui possiamo forzare il compilatore chiedendo di convertire il valore contenuto in una variabile di range più grande in una di range più piccolo perchè sappiamo che il valore contenuto è rappresentabile dal tipo più piccolo (Narrowing conversion). Questa operazione si chiama casting esplicito. Un esempio:
valByte = (byte) valInt;
Occorre prestare attenzione ad un casting di questo tipo. Con riferimento all'istruzione precedente, se in valInt
dovesse essere contenuto un valore non appartenente al range -128,+127 avremmo una perdita di informazioni con un risultato imprevedibile.
Vediamo adesso cosa accade quando differenti tipi sono coinvolti nell'ambito di una espressione aritmetica. Consideriamo il seguente frammento di codice:
short s = 2;
int i = 8;
float f = 12.8f;
double d = 12.5;
if(-s*i >= f/d)
System.out.println(">=");
else
System.out.println("
Il compilatore, prima di poter valutare l'espressione numerica contenuta nell'istruzione if
, deve effettuare una serie di Widening conversion (per questo si parla di promozione del tipo) per ottenere il giusto tipo per il risultato dell'espressione.
Regole di promozione
Le regole che governano la promozione del tipo per le espressioni aritmetiche sono legate ai due tipi di operatori unario e binario. In caso di operatore binario (+,-,~) se l'operando numerico è un byte
o short
, viene convertito in un int
, altrimenti nessuna conversione è applicata. Gli operatori unari ++,-- non sono soggetti a conversione.
Per gli operatori binari (+,-,*,&,%,...) si applicano le seguenti regole:
- se uno degli operandi è un
double
l'altro operando è convertito ad un double; - se uno degli operandi è un
float
l'altro operando è convertito ad unfloat
; - se uno degli operandi è un
long
, l'altro operando è convertito ad unlong
; - diversamente entrambi gli operandi sono convertiti a
int
.
Cosi in riferimento al codice precedente abbiamo i seguenti passi:
- s è promossa ad
int
e negata; - s è un
int
ed è moltiplicata per i ottenendo unint
, nessuna conversione necessaria; - f è promossa a
double
e quindi divisa per d che è undouble
, il risultato è undouble
; - abbiamo un
int
confrontato con un double attraverso l'operatore >= , l'int
è convertito adouble
ed i valori sono confrontati restituendo unboolean
.