Comment puis-je vérifier si la multiplication de deux nombres en Java entraînera un débordement?

101

Je veux gérer le cas particulier où la multiplication de deux nombres ensemble provoque un débordement. Le code ressemble à ceci:

int a = 20;
long b = 30;

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

C'est une version simplifiée. Dans le programme réel aet bproviennent ailleurs au moment de l'exécution. Ce que je veux réaliser est quelque chose comme ceci:

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

Comment me suggérez-vous de coder au mieux cela?

Mise à jour: aet bsont toujours non négatifs dans mon scénario.

Steve McLeod
la source
6
Il est dommage que Java ne fournisse pas un accès indirect à l' indicateur de débordement du processeur , comme cela se fait en C # .
Drew Noakes

Réponses:

92

Java 8 a Math.multiplyExact, Math.addExactetc. pour les entiers et les longs. Ceux-ci lancent un ArithmeticExceptiondébordement non contrôlé .

bcoughlan
la source
59

Si aet bsont tous les deux positifs, vous pouvez utiliser:

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

Si vous devez gérer à la fois des nombres positifs et négatifs, c'est plus compliqué:

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
}

Voici un petit tableau que j'ai créé pour vérifier cela, en prétendant que le débordement se produit à -10 ou +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
John Kugelman
la source
Je dois mentionner que a et b sont toujours non négatifs dans mon scénario, ce qui simplifierait quelque peu cette approche.
Steve McLeod
3
Je pense que cela peut échouer dans un cas: a = -1 et b = 10. L'expression maximum / a résulte en Integer.MIN_VALUE et détecte le débordement quand il n'en existait pas
Kyle
C'est vraiment sympa. Pour ceux qui se demandent, la raison pour laquelle cela fonctionne est que pour un entier n, n > xc'est la même chose que n > floor(x). Pour les entiers positifs, la division fait un plancher implicite. (Pour les nombres négatifs, il arrondit à la place)
Thomas Ahle
Pour résoudre le problème a = -1et b = 10, voir ma réponse ci-dessous.
Jim Pivarski
17

Il existe des bibliothèques Java qui fournissent des opérations arithmétiques sûres, qui vérifient les longs débordements / sous-débordements. Par exemple, LongMath.checkedMultiply de Guava (long a, long b) renvoie le produit de aet b, à condition qu'il ne déborde pas, et renvoie ArithmeticExceptionsi a * bdéborde en longarithmétique signée .

reprogrammeur
la source
4
C'est la meilleure réponse - utilisez une bibliothèque qui a été implémentée par des personnes qui comprennent vraiment l'arithmétique des machines en Java et qui a été testée par de nombreuses personnes. N'essayez pas d'écrire le vôtre ou d'utiliser l'un des codes non testés à moitié cuits publiés dans les autres réponses!
Rich
@Enerccio - Je ne comprends pas votre commentaire. Êtes-vous en train de dire que Guava ne fonctionnera pas sur tous les systèmes? Je peux vous assurer que cela fonctionnera partout où Java fonctionne. Êtes-vous en train de dire que la réutilisation du code est une mauvaise idée en général? Je ne suis pas d'accord si oui.
Riche du
2
@Rich je dis qu'inclure une énorme bibliothèque pour pouvoir utiliser une fonction est une mauvaise idée.
Enerccio
Pourquoi? Si vous écrivez une application volumineuse, par exemple pour une entreprise, un JAR supplémentaire sur le chemin de classe ne fera aucun mal et Guava contient beaucoup de code très utile. Il est bien préférable de réutiliser leur code soigneusement testé que d'essayer d'écrire votre propre version (ce que je suppose est ce que vous recommandez?). Si vous écrivez dans un environnement où un JAR supplémentaire va coûter très cher (où? Java intégré?), Vous devriez peut-être extraire uniquement cette classe de Guava. Est-il préférable de copier une réponse non testée de StackOverflow que de copier le code soigneusement testé de Guava?
Riche du
Lancer une exception n'est-il pas un peu exagéré pour quelque chose qu'un si-alors conditionnel peut gérer?
victtim
6

Vous pouvez utiliser java.math.BigInteger à la place et vérifier la taille du résultat (je n'ai pas testé le code):

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()
}
Ulf Lindback
la source
7
Je trouve cette solution plutôt lente
nothrow
C'est probablement la meilleure façon de le faire, cependant. J'ai supposé qu'il s'agissait d'une application numérique, c'est pourquoi je ne l'ai pas recommandée à la légère, mais c'est probablement la meilleure façon de résoudre ce problème.
Stefan Kendall
2
Je ne suis pas sûr que vous puissiez utiliser l'opérateur '>' avec BigInteger. la méthode compareTo doit être utilisée.
Pierre
changé en compareTo, et la vitesse peut avoir de l'importance ou non, dépend des circonstances dans lesquelles le code sera utilisé.
Ulf Lindback
5

Utilisez des logarithmes pour vérifier la taille du résultat.

Marque haute performance
la source
voulez-vous dire ceil(log(a)) + ceil(log(b)) > log(Long.MAX):?
Thomas Jung
1
Je l'ai vérifié. Pour les petites valeurs, il est 20% plus rapide que BigInteger et pour les valeurs proches de MAX, il est presque identique (5% plus rapide). Le code de Yossarian est le plus rapide (95% et 75% plus rapide que BigInteger).
Thomas Jung
Je soupçonne plutôt que cela peut échouer dans certains cas.
Tom Hawtin - tackline
Rappelez-vous qu'un journal entier ne compte en fait que le nombre de zéros non significatifs, et vous pouvez optimiser certains cas courants (par exemple si ((a | b) & 0xffffffff00000000L) == 0) vous savez que vous êtes en sécurité). D'autre part, à moins que vous ne puissiez réduire votre optimisation à 30/40 cycles d'horloge pour les cas les plus courants, la méthode de John Kugelman fonctionnera probablement mieux (une division entière équivaut à environ 2 bits / cycle d'horloge, comme je me souviens).
Neil Coffey
PS Sorr, je pense que j'ai besoin d'un peu supplémentaire dans le masque ET (0xffffffff80000000L) - il est un peu tard, mais vous voyez l'idée ...
Neil Coffey
4

Java a-t-il quelque chose comme int.MaxValue? Si oui, essayez

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

edit: vu Long.MAX_VALUE en question

pas jeter
la source
Je n'ai pas voté contre mais Math.Abs(a)ne fonctionne pas si ac'est le cas Long.MIN_VALUE.
John Kugelman
@John - a et b sont> 0. Je pense que l'approche de Yossarian (b! = 0 && a> Long.MAX_VALUE / b) est la meilleure.
Thomas Jung
@Thomas, a et b sont> = 0, c'est-à-dire non négatifs.
Steve McLeod
2
D'accord, mais dans ce cas, pas besoin des Abs. Si des nombres négatifs sont autorisés, cela échoue pour au moins un cas d'arête. C'est tout ce que je dis, juste être pinailleur.
John Kugelman
en Java, vous devez utiliser Math.abs, pas Math.Abs ​​(C # guy?)
dfa
4

Voici le moyen le plus simple auquel je puisse penser

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"
}
Naren
la source
3

Volé à jruby

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

MISE À JOUR: Ce code est court et fonctionne bien; cependant, il échoue pour a = -1, b = Long.MIN_VALUE.

Une amélioration possible:

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

Notez que cela rattrapera certains débordements sans aucune division.

rogerdpack
la source
Vous pouvez utiliser Long.signum au lieu de Math.signum
aditsu quitte car SE est EVIL
3

Comme cela a été souligné, Java 8 a des méthodes Math.xxxExact qui lèvent des exceptions en cas de débordement.

Si vous n'utilisez pas Java 8 pour votre projet, vous pouvez toujours «emprunter» leurs implémentations qui sont assez compactes.

Voici quelques liens vers ces implémentations dans le référentiel de code source JDK, aucune garantie que ceux-ci resteront valides, mais dans tous les cas, vous devriez pouvoir télécharger la source JDK et voir comment ils font leur magie à l'intérieur de la 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

etc.

MISE À JOUR: les liens invalides vers le site Web tiers ont été remplacés par des liens vers les référentiels Mercurial d'Open JDK.

StFS
la source
2

Je ne sais pas pourquoi personne ne cherche une solution comme:

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

Choisissez a pour être le plus grand des deux nombres.

fastcodejava
la source
2
Je ne pense pas que ce asoit le plus grand ou le plus petit qui importe ?
Thomas Ahle
2

J'aimerais m'appuyer sur la réponse de John Kugelman sans la remplacer en la modifiant directement. Cela fonctionne pour son cas de test ( MIN_VALUE = -10, MAX_VALUE = 10) en raison de la symétrie de MIN_VALUE == -MAX_VALUE, ce qui n'est pas le cas pour les entiers complémentaires à deux. En réalité, 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)

Lorsqu'elle est appliquée au vrai MIN_VALUEet MAX_VALUE, la réponse de John Kugelman produit un cas de débordement quand a == -1et b ==quoi que ce soit d'autre (point soulevé en premier par Kyle). Voici un moyen de résoudre ce problème:

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
}

Ce n'est pas une solution générale pour tout MIN_VALUEet MAX_VALUE, mais elle est générale pour Java Longet Integeret toute valeur de aet b.

Jim Pivarski
la source
J'ai juste pensé que cela le compliquerait inutilement, car cette solution ne fonctionne que si MIN_VALUE = -MAX_VALUE - 1, pas dans n'importe quel autre cas (y compris votre exemple de cas de test). J'aurais beaucoup à changer.
Jim Pivarski
1
Pour des raisons allant au-delà des besoins de l'affiche originale; pour des gens comme moi qui ont trouvé cette page car ils avaient besoin d'une solution pour un cas plus général (gestion des nombres négatifs et pas strictement Java 8). En fait, puisque cette solution n'implique aucune fonction au-delà de l'arithmétique et de la logique pures, elle pourrait également être utilisée pour C ou d'autres langages.
Jim Pivarski
1

Peut être:

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

Pas sûr de cette "solution".

Edit: Ajouté b! = 0.

Avant de voter contre : a * b / b ne sera pas optimisé. Ce serait un bogue du compilateur. Je ne vois toujours pas de cas où le bug de débordement peut être masqué.

Thomas Jung
la source
Échoue également lorsque le débordement provoque une boucle parfaite.
Stefan Kendall
Avez-vous un exemple de ce que vous vouliez dire?
Thomas Jung
Je viens d'écrire un petit test: l'utilisation de BigInteger est 6 fois plus lente que l'utilisation de cette approche de division. Je suppose donc que des vérifications supplémentaires pour les boîtiers d'angle en valent la peine en termes de performances.
mhaller
Je ne sais pas grand-chose sur les compilateurs java, mais une expression comme a * b / best susceptible d'être optimisée adans de nombreux autres contextes.
SingleNegationElimination
TokenMacGuy - il ne peut pas être optimisé comme ça s'il y a un risque de débordement.
Tom Hawtin - tackline
1

peut-être que cela vous aidera:

/**
 * @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;
    }
}
dfa
la source
N'aidera pas comme l'un des opérandes est long.
Tom Hawtin - tackline
1
Avez-vous même testé cela? Pour toutes les valeurs importantes mais non débordantes de a & b, cela échouera en raison d'erreurs d'arrondi dans la version double de la multiplication (essayez par exemple 123456789123L et 74709314L). Si vous ne comprenez pas l'arithmétique de la machine, deviner une réponse à ce type de question précise est pire que de ne pas répondre, car cela induira les gens en erreur.
Rich
-1

c / c ++ (long * long):

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

java (int * int, désolé je n'ai pas trouvé int64 dans 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. enregistrez le résultat en grand type (int * int met le résultat à long, long * long mis à int64)

2.cmp résultat >> bits et résultat >> (bits - 1)

xuan
la source