Bene, per quanto riguarda i tipi interi primitivi, Java non gestisce affatto Over / Underflow (per il float e il doppio il comportamento è diverso, arriverà a +/- infinito proprio come IEEE-754 impone).
Quando aggiungi due int, non otterrai alcuna indicazione quando si verifica un overflow. Un metodo semplice per verificare l'overflow consiste nell'utilizzare il tipo più grande successivo per eseguire effettivamente l'operazione e verificare se il risultato è ancora nell'intervallo per il tipo di origine:
public int addWithOverflowCheck(int a, int b) {
// the cast of a is required, to make the + work with long precision,
// if we just added (a + b) the addition would use int precision and
// the result would be cast to long afterwards!
long result = ((long) a) + b;
if (result > Integer.MAX_VALUE) {
throw new RuntimeException("Overflow occured");
} else if (result < Integer.MIN_VALUE) {
throw new RuntimeException("Underflow occured");
}
// at this point we can safely cast back to int, we checked before
// that the value will be withing int's limits
return (int) result;
}
Che cosa faresti al posto delle clausole di lancio, dipende dai requisiti delle tue applicazioni (lancio, scarica al minimo / massimo o registra semplicemente). Se vuoi rilevare l'overflow in operazioni lunghe, sei sfortunato con le primitive, usa invece BigInteger.
Modifica (21-05-2014): poiché questa domanda sembra essere citata abbastanza frequentemente e ho dovuto risolvere da solo lo stesso problema, è abbastanza facile valutare la condizione di overflow con lo stesso metodo con cui una CPU calcolerebbe il suo flag V.
Fondamentalmente è un'espressione booleana che coinvolge sia il segno di entrambi gli operandi che il risultato:
/**
* Add two int's with overflow detection (r = s + d)
*/
public static int add(final int s, final int d) throws ArithmeticException {
int r = s + d;
if (((s & d & ~r) | (~s & ~d & r)) < 0)
throw new ArithmeticException("int overflow add(" + s + ", " + d + ")");
return r;
}
In java è più semplice applicare l'espressione (in if) a tutti i 32 bit e controllare il risultato usando <0 (questo testerà effettivamente il bit del segno). Il principio funziona esattamente allo stesso modo per tutti i tipi primitivi interi , modificando tutte le dichiarazioni del metodo sopra in long lo fa funzionare a lungo.
Per tipi più piccoli, a causa della conversione implicita in int (per i dettagli vedere JLS per operazioni bit a bit), invece di controllare <0, il controllo deve mascherare esplicitamente il bit di segno (0x8000 per operandi brevi, 0x80 per operandi byte, regolare i cast e la dichiarazione dei parametri opportunamente):
/**
* Subtract two short's with overflow detection (r = d - s)
*/
public static short sub(final short d, final short s) throws ArithmeticException {
int r = d - s;
if ((((~s & d & ~r) | (s & ~d & r)) & 0x8000) != 0)
throw new ArithmeticException("short overflow sub(" + s + ", " + d + ")");
return (short) r;
}
(Si noti che l'esempio sopra utilizza l'espressione necessità di sottrarre il rilevamento di overflow)
Quindi come / perché funzionano queste espressioni booleane? Innanzitutto, un certo pensiero logico rivela che un overflow può verificarsi solo se i segni di entrambi gli argomenti sono gli stessi. Perché, se un argomento è negativo e uno positivo, il risultato (di add) deve essere più vicino a zero, o nel caso estremo un argomento è zero, lo stesso dell'altro argomento. Poiché gli argomenti da soli non possono creare una condizione di overflow, neanche la loro somma può creare un overflow.
Quindi cosa succede se entrambi gli argomenti hanno lo stesso segno? Diamo un'occhiata al caso entrambi sono positivi: l'aggiunta di due argomenti che creano una somma maggiore dei tipi MAX_VALUE, produrrà sempre un valore negativo, quindi si verifica un overflow se arg1 + arg2> MAX_VALUE. Ora il valore massimo che potrebbe risultare sarebbe MAX_VALUE + MAX_VALUE (nel caso estremo entrambi gli argomenti sono MAX_VALUE). Per un byte (esempio) che significherebbe 127 + 127 = 254. Osservando le rappresentazioni dei bit di tutti i valori che possono derivare dall'aggiunta di due valori positivi, si scopre che quelli che superano (da 128 a 254) hanno tutti impostato il bit 7, mentre tutto quello che non trabocca (da 0 a 127) ha il bit 7 (più in alto, segno) cancellato. Questo è esattamente ciò che controlla la prima parte (destra) dell'espressione:
if (((s & d & ~r) | (~s & ~d & r)) < 0)
(~ s & ~ d & r) diventa vero, solo se entrambi gli operandi (s, d) sono positivi e il risultato (r) è negativo (l'espressione funziona su tutti e 32 i bit, ma l'unico bit a cui siamo interessati è il bit (segno) più in alto, che viene verificato da <0).
Ora, se entrambi gli argomenti sono negativi, la loro somma non può mai essere più vicina allo zero di qualsiasi argomento, la somma deve essere più vicina al meno infinito. Il valore più estremo che possiamo produrre è MIN_VALUE + MIN_VALUE, che (sempre per esempio byte) mostra che per qualsiasi valore compreso nell'intervallo (da -1 a -128) viene impostato il bit di segno, mentre ogni possibile valore di overflow (da -129 a -256 ) ha il bit di segno cancellato. Quindi il segno del risultato rivela di nuovo la condizione di overflow. Questo è ciò che la metà sinistra (s & d & ~ r) controlla per il caso in cui entrambi gli argomenti (s, d) sono negativi e un risultato positivo. La logica è ampiamente equivalente al caso positivo; tutti i pattern di bit che possono derivare dall'aggiunta di due valori negativi avranno il bit di segno cancellato se e solo se si è verificato un underflow.