Come posso verificare se la moltiplicazione di due numeri in Java provocherà un overflow?


101

Voglio gestire il caso speciale in cui la moltiplicazione di due numeri insieme causa un overflow. Il codice ha un aspetto simile a questo:

int a = 20;
long b = 30;

// if a or b are big enough, this result will silently overflow
long c = a * b;

Questa è una versione semplificata. Nel programma reale ae bsono reperiti altrove in fase di esecuzione. Quello che voglio ottenere è qualcosa del genere:

long c;
if (a * b will overflow) {
    c = Long.MAX_VALUE;
} else {
    c = a * b;
}

Come suggerisci di codificarlo al meglio?

Aggiornamento: ae bsono sempre non negativi nel mio scenario.


6
È un peccato che Java non fornisca l'accesso indiretto al flag di overflow della CPU , come avviene in C # .
Drew Noakes

Risposte:


92

Java 8 ha Math.multiplyExact, Math.addExactecc. Per int e long. Questi generano un ArithmeticExceptionoverflow non controllato .


59

Se ae bsono entrambi positivi, puoi usare:

if (a != 0 && b > Long.MAX_VALUE / a) {
    // Overflow
}

Se devi gestire sia numeri positivi che negativi, è più complicato:

long maximum = Long.signum(a) == Long.signum(b) ? Long.MAX_VALUE : Long.MIN_VALUE;

if (a != 0 && (b > 0 && b > maximum / a ||
               b < 0 && b < maximum / a))
{
    // Overflow
}

Ecco un tavolino che ho preparato per verificarlo, fingendo che l'overflow avvenga a -10 o +10:

a =  5   b =  2     2 >  10 /  5
a =  2   b =  5     5 >  10 /  2
a = -5   b =  2     2 > -10 / -5
a = -2   b =  5     5 > -10 / -2
a =  5   b = -2    -2 < -10 /  5
a =  2   b = -5    -5 < -10 /  2
a = -5   b = -2    -2 <  10 / -5
a = -2   b = -5    -5 <  10 / -2

Dovrei ricordare che aeb sono sempre non negativi nel mio scenario, il che semplificherebbe in qualche modo questo approccio.
Steve McLeod

3
Penso che questo possa fallire un caso: a = -1 eb = 10. L'espressione massima / a risulta in Integer.MIN_VALUE e rileva l'overflow quando non esisteva nessuno
Kyle

Questo è davvero carino. Per coloro che si chiedono, il motivo per cui funziona è che per intero n, n > xè lo stesso di n > floor(x). Per gli interi positivi la divisione fa un valore minimo implicito. (Per i numeri negativi si arrotonda invece per
eccesso

Per risolvere il problema a = -1e b = 10, vedere la mia risposta di seguito.
Jim Pivarski

17

Esistono librerie Java che forniscono operazioni aritmetiche sicure, che controllano overflow / underflow lunghi. Ad esempio, LongMath.checkedMultiply di Guava (long a, long b) restituisce il prodotto di ae b, a condizione che non esegua l' overflow, e genera ArithmeticExceptionse a * boverflow longnell'aritmetica con segno.


4
Questa è la risposta migliore: usa una libreria che è stata implementata da persone che capiscono veramente l'aritmetica delle macchine in Java e che è stata testata da molte persone. Non tentare di scriverne uno tuo o di usare il codice non testato a metà pubblicato nelle altre risposte!
Rich

@Enerccio - Non capisco il tuo commento. Stai dicendo che Guava non funzionerà su tutti i sistemi? Posso assicurarti che funzionerà ovunque faccia Java. Stai dicendo che riutilizzare il codice è una cattiva idea in generale? Non sono d'accordo se è così.
Rich

2
@Rich sto dicendo che includere un'enorme libreria in modo da poter utilizzare una funzione è una cattiva idea.
Enerccio

Perché? Se stai scrivendo un'applicazione di grandi dimensioni, ad esempio per un'azienda, un JAR in più sul classpath non farà alcun danno e Guava contiene un sacco di codice molto utile. È molto meglio riutilizzare il loro codice accuratamente testato piuttosto che provare a scrivere la tua versione dello stesso (che presumo sia quello che consigli?). Se stai scrivendo in un ambiente in cui un JAR extra sarà molto costoso (dove? Java incorporato?), Allora forse dovresti estrarre solo questa classe da Guava. Copiare una risposta non testata da StackOverflow è meglio che copiare il codice accuratamente testato di Guava?
Rich

Lanciare un'eccezione non è un po 'eccessivo per qualcosa che un se-allora condizionale può gestire?
victtim

6

Puoi invece usare java.math.BigInteger e controllare la dimensione del risultato (non hai testato il codice):

BigInteger bigC = BigInteger.valueOf(a) * multiply(BigInteger.valueOf(b));
if(bigC.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
  c = Long.MAX_VALUE;
} else {
  c = bigC.longValue()
}

7
Trovo questa soluzione piuttosto lenta
nothrow

Probabilmente è il modo migliore per farlo, però. Ho pensato che fosse un'applicazione numerica, motivo per cui non l'ho consigliata a priori, ma questo è probabilmente il modo migliore per risolvere questo problema.
Stefan Kendall

2
Non sono sicuro che tu possa usare l'operatore ">" con BigInteger. dovrebbe essere usato il metodo compareTo.
Pierre

modificato in compareTo, e la velocità potrebbe avere importanza o meno, dipende dalle circostanze in cui verrà utilizzato il codice.
Ulf Lindback

5

Usa i logaritmi per controllare la dimensione del risultato.


intendi ceil(log(a)) + ceil(log(b)) > log(Long.MAX):?
Thomas Jung

1
L'ho controllato. Per valori piccoli è il 20% più veloce di BigInteger e per valori vicini a MAX è quasi lo stesso (5% più veloce). Il codice di Yossarian è il più veloce (95% e 75% più veloce di BigInteger).
Thomas Jung

Sospetto piuttosto che in alcuni casi possa fallire.
Tom Hawtin - tackline

Ricorda che un registro di numeri interi sta effettivamente contando solo il numero di zeri iniziali e puoi ottimizzare alcuni casi comuni (ad esempio se ((a | b) & 0xffffffff00000000L) == 0) sai di essere al sicuro). D'altra parte, a meno che tu non possa ottenere la tua ottimizzazione fino a cicli di clock di 30/40 per i casi più comuni, il metodo di John Kugelman probabilmente funzionerà meglio (una divisione intera è di circa 2 bit / ciclo di clock come ricordo).
Neil Coffey

PS Sorr, penso di aver bisogno di un bit in più impostato nella maschera AND (0xffffffff80000000L) - è un po 'tardi, ma hai avuto l'idea ...
Neil Coffey

4

Java ha qualcosa come int.MaxValue? Se sì, allora prova

if (b != 0 && Math.abs(a) > Math.abs(Long.MAX_VALUE / b))
{
 // it will overflow
}

modifica: visto Long.MAX_VALUE in questione


Non ho downvote ma Math.Abs(a)non funziona se lo aè Long.MIN_VALUE.
John Kugelman

@ John - aeb sono> 0. Penso che l'approccio di Yossarian (b! = 0 && a> Long.MAX_VALUE / b) sia il migliore.
Thomas Jung

@Thomas, aeb sono> = 0, cioè non negativi.
Steve McLeod

2
Giusto, ma in quel caso non servono gli addominali. Se sono consentiti numeri negativi, ciò non riesce per almeno un caso limite. È tutto quello che sto dicendo, solo essere pignolo.
John Kugelman

in Java devi usare Math.abs, non Math.Abs ​​(C # guy?)
dfa

4

Ecco il modo più semplice a cui riesco a pensare

int a = 20;
long b = 30;
long c = a * b;

if(c / b == a) {
   // Everything fine.....no overflow
} else {
   // Overflow case, because in case of overflow "c/b" can't equal "a"
}

3

Rubato a jruby

    long result = a * b;
    if (a != 0 && result / a != b) {
       // overflow
    }

AGGIORNAMENTO: questo codice è breve e funziona bene; tuttavia, non riesce per a = -1, b = Long.MIN_VALUE.

Un possibile miglioramento:

long result = a * b;
if( (Math.signum(a) * Math.signum(b) != Math.signum(result)) || 
    (a != 0L && result / a != b)) {
    // overflow
}

Nota che questo catturerà alcuni overflow senza alcuna divisione.


Puoi usare Long.signum invece di Math.signum
aditsu esci perché SE è EVIL

3

Come è stato sottolineato, Java 8 ha metodi Math.xxxExact che generano eccezioni in caso di overflow.

Se non stai usando Java 8 per il tuo progetto, puoi comunque "prendere in prestito" le loro implementazioni che sono piuttosto compatte.

Ecco alcuni collegamenti a queste implementazioni nel repository del codice sorgente JDK, nessuna garanzia che rimarranno valide ma in ogni caso dovresti essere in grado di scaricare il sorgente JDK e vedere come fanno la loro magia all'interno della java.lang.Mathclasse.

Math.multiplyExact(long, long) http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/lang/Math.java#l925

Math.addExact(long, long) http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/lang/Math.java#l830

ecc. ecc.

AGGIORNATO: disattivato i collegamenti non validi a siti Web di terze parti ai collegamenti ai repository Mercurial di Open JDK.


2

Non sono sicuro del motivo per cui nessuno sta guardando una soluzione come:

if (Long.MAX_VALUE/a > b) {
     // overflows
} 

Scegli a per essere più grande dei due numeri.


2
Non penso sia importante se aè il più grande o il più piccolo?
Thomas Ahle

2

Vorrei basarmi sulla risposta di John Kugelman senza sostituirla modificandola direttamente. Funziona per il suo test case ( MIN_VALUE = -10, MAX_VALUE = 10) a causa della simmetria di MIN_VALUE == -MAX_VALUE, che non è il caso degli interi in complemento a due. In realtà, MIN_VALUE == -MAX_VALUE - 1.

scala> (java.lang.Integer.MIN_VALUE, java.lang.Integer.MAX_VALUE)
res0: (Int, Int) = (-2147483648,2147483647)

scala> (java.lang.Long.MIN_VALUE, java.lang.Long.MAX_VALUE)
res1: (Long, Long) = (-9223372036854775808,9223372036854775807)

Quando applicata al vero MIN_VALUEe MAX_VALUE, la risposta di John Kugelman produce un caso in overflow quando a == -1e b ==qualsiasi altra cosa (punto sollevato per primo da Kyle). Ecco un modo per risolverlo:

long maximum = Long.signum(a) == Long.signum(b) ? Long.MAX_VALUE : Long.MIN_VALUE;

if ((a == -1 && b == Long.MIN_VALUE) ||
    (a != -1 && a != 0 && ((b > 0 && b > maximum / a) ||
                           (b < 0 && b < maximum / a))))
{
    // Overflow
}

Non è una soluzione generale per qualsiasi MIN_VALUEe MAX_VALUE, ma è generale per Java Longe Integere qualsiasi valore di ae b.


Ho solo pensato che lo avrebbe complicato inutilmente, poiché questa soluzione funziona solo se MIN_VALUE = -MAX_VALUE - 1, non in qualsiasi altro caso (incluso il tuo caso di test di esempio). Dovrei cambiare molto.
Jim Pivarski

1
Per ragioni che esulano dalle esigenze del poster originale; per chi come me ha trovato questa pagina perché aveva bisogno di una soluzione per un caso più generale (gestire numeri negativi e non strettamente Java 8). Infatti, poiché questa soluzione non coinvolge alcuna funzione oltre alla pura aritmetica e logica, potrebbe essere utilizzata anche per C o altri linguaggi.
Jim Pivarski

1

Può essere:

if(b!= 0 && a * b / b != a) //overflow

Non sono sicuro di questa "soluzione".

Modifica: aggiunto b! = 0.

Prima di downvote : a * b / b non sarà ottimizzato. Questo sarebbe un bug del compilatore. Continuo a non vedere un caso in cui il bug di overflow possa essere mascherato.


Inoltre fallisce quando l'overflow causa un loop perfetto.
Stefan Kendall

Hai un esempio di cosa intendevi?
Thomas Jung

Ho appena scritto un piccolo test: l'utilizzo di BigInteger è 6 volte più lento rispetto all'utilizzo di questo approccio di divisione. Quindi presumo che i controlli aggiuntivi per i casi d'angolo valgano la pena dal punto di vista delle prestazioni.
mhaller

Non so molto sui compilatori java, ma a * b / bè probabile che un'espressione come quella venga ottimizzata solo ain molti altri contesti.
SingleNegationElimination

TokenMacGuy: non può essere ottimizzato in questo modo se c'è il pericolo di overflow.
Tom Hawtin - tackline

1

forse questo ti aiuterà:

/**
 * @throws ArithmeticException on integer overflow
 */
static long multiply(long a, long b) {
    double c = (double) a * b;
    long d = a * b;

    if ((long) c != d) {
        throw new ArithmeticException("int overflow");
    } else {
        return d;
    }
}

Non aiuterà come uno degli operandi long.
Tom Hawtin - tackline

1
Hai anche provato questo? Per tutti i valori di a & b grandi ma non eccessivi, ciò non andrà a buon fine a causa di errori di arrotondamento nella versione doppia del moltiplicatore (provare ad esempio 123456789123L e 74709314L). Se non capisci l'aritmetica della macchina, indovinare una risposta a questo tipo di domanda precisa è peggio che non rispondere, in quanto fuorvierà le persone.
Rich

-1

c / c ++ (lungo * lungo):

const int64_ w = (int64_) a * (int64_) b;    
if ((long) (w >> sizeof(long) * 8) != (long) w >> (sizeof(long) * 8 - 1))
    // overflow

java (int * int, scusa non ho trovato int64 in java):

const long w = (long) a * (long) b;    
int bits = 32; // int is 32bits in java    
if ( (int) (w >> bits) != (int) (w >> (bits - 1))) {
   // overflow
}

1.Salva il risultato in caratteri grandi (int * int mette il risultato a long, long * long mette a int64)

2.cmp risultato >> bit e risultato >> (bit - 1)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.