web-dev-qa-db-fra.com

Comment Java traite-t-il les sous-débits et les débordements entiers et comment le vérifier?

Comment Java gère-t-il les sous-débits et les débordements entiers?

À partir de là, comment vérifieriez-vous/testez-vous que cela se produit?

195
KushalP

S'il déborde, il retourne à la valeur minimale et continue à partir de là. Si elle est sous-jacente, elle retourne à la valeur maximale et continue à partir de là.

Vous pouvez vérifier cela au préalable comme suit:

public static boolean willAdditionOverflow(int left, int right) {
    if (right < 0 && right != Integer.MIN_VALUE) {
        return willSubtractionOverflow(left, -right);
    } else {
        return (~(left ^ right) & (left ^ (left + right))) < 0;
    }
}

public static boolean willSubtractionOverflow(int left, int right) {
    if (right < 0) {
        return willAdditionOverflow(left, -right);
    } else {
        return ((left ^ right) & (left ^ (left - right))) < 0;
    }
}

(vous pouvez remplacer int par long pour effectuer les mêmes vérifications que pour long)

Si vous pensez que cela peut se produire plus souvent, envisagez d'utiliser un type de données ou un objet pouvant stocker des valeurs plus grandes, par ex. long ou peut-être Java.math.BigInteger . Le dernier ne déborde pas, pratiquement, la mémoire JVM disponible est la limite.


Si vous êtes déjà sur Java8, vous pouvez utiliser les nouvelles méthodes Math#addExact() _ et Math#subtractExact() qui jetteront une ArithmeticException en dépassement.

public static boolean willAdditionOverflow(int left, int right) {
    try {
        Math.addExact(left, right);
        return false;
    } catch (ArithmeticException e) {
        return true;
    }
}

public static boolean willSubtractionOverflow(int left, int right) {
    try {
        Math.subtractExact(left, right);
        return false;
    } catch (ArithmeticException e) {
        return true;
    }
}

Le code source peut être trouvé ici et ici respectivement.

Bien sûr, vous pouvez également les utiliser immédiatement au lieu de les masquer dans une méthode utilitaire boolean.

198
BalusC

Eh bien, pour ce qui est des types entiers primitifs, Java ne gère pas du tout les dépassements/dépassements (du fait que le comportement est différent du double, il va affleurer à l'infini comme le prescrit IEEE-754).

Lors de l'ajout de deux int, vous n'obtiendrez aucune indication en cas de débordement. Une méthode simple pour vérifier le débordement consiste à utiliser le type immédiatement supérieur pour effectuer réellement l'opération et à vérifier si le résultat est toujours dans la plage pour le type de source:

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;
}

Ce que vous feriez à la place des clauses de rejet dépend des exigences de vos applications (lancer, affleurement à min/max ou simplement enregistrer quoi que ce soit). Si vous souhaitez détecter un débordement sur de longues opérations, si vous n'avez pas de chance avec les primitives, utilisez BigInteger à la place.


Edit (2014-05-21): Étant donné que cette question semble être évoquée assez fréquemment et que je devais résoudre le même problème moi-même, il est assez facile d'évaluer la condition de dépassement de capacité par la même méthode qu'une CPU calcule son drapeau V.

C'est fondamentalement une expression booléenne qui implique le signe des deux opérandes ainsi que le résultat:

/**
 * 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;
}

Dans Java, il est plus simple d'appliquer l'expression (dans le if) aux 32 bits entiers et de vérifier le résultat à l'aide de <0 (ceci testera effectivement le bit de signe). Le principe fonctionne exactement de la même manière pour tous les types primitifs entiers, si vous modifiez toutes les déclarations de la méthode ci-dessus, cela fonctionnera longtemps.

Pour les types plus petits, en raison de la conversion implicite en int (voir JLS pour les opérations au niveau des bits pour plus de détails), au lieu de vérifier <0, la vérification doit masquer le bit de signe de manière explicite (0x8000 pour les opérandes courts, 0x80 pour les opérandes d'octet, ajuster les transferts et la déclaration des paramètres, appropriée):

/**
 * 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;
}

(Notez que l'exemple ci-dessus utilise l'expression need for soustraction détection de débordement)


Alors, comment/pourquoi ces expressions booléennes fonctionnent-elles? Premièrement, certaines réflexions logiques révèlent qu'un dépassement de capacité peut niquement se produire si les signes des deux arguments sont identiques. Parce que, si un argument est négatif et un positif, le résultat (de add) doit soit plus proche de zéro, ou dans le cas extrême, un argument est égal à zéro, identique à l'autre argument. Puisque les arguments par eux-mêmes ne peut pas créent une condition de débordement, leur somme ne peut pas créer de débordement non plus.

Alors que se passe-t-il si les deux arguments ont le même signe? Jetons un coup d'œil au cas. Les deux sont positifs: l'ajout de deux arguments créant une somme supérieure aux types MAX_VALUE donnera toujours une valeur négative, ce qui entraîne un débordement if arg1 + arg2> MAX_VALUE. La valeur maximale qui pourrait en résulter serait alors MAX_VALUE + MAX_VALUE (les deux arguments, dans le cas extrême, sont MAX_VALUE). Pour un octet (exemple), cela signifierait 127 + 127 = 254. En regardant les représentations binaires de toutes les valeurs pouvant résulter de l’ajout de deux valeurs positives, on constate que celles qui dépassent (128 à 254) ont toutes le bit 7 défini, alors que tout ce qui ne déborde pas (0 à 127) a le bit 7 (top, signe) effacé. C'est exactement ce que la première (à droite) partie de l'expression vérifie:

if (((s & d & ~r) | (~s & ~d & r)) < 0)

(~ s & ~ d & r) devient vrai, seulement si, les deux opérandes (s, d) sont positifs et le résultat (r) est négatif (l'expression fonctionne sur les 32 bits, mais seul le bit qui nous intéresse est le bit le plus haut (signe), qui est vérifié par le <0).

Maintenant, si les deux arguments sont négatifs, leur somme ne peut jamais être plus proche de zéro que n'importe lequel des arguments, la somme doit être plus proche de moins l'infini. La valeur la plus extrême que nous puissions produire est MIN_VALUE + MIN_VALUE, ce qui (encore une fois pour un exemple d'octet) indique que, pour toute valeur comprise dans la plage (-1 à -128), le bit de signe est défini, tandis que toute valeur de débordement possible (-129 à -256) ) a le bit de signe effacé. Donc, le signe du résultat révèle à nouveau la condition de débordement. C’est ce que la moitié gauche (S & D & ~ r) vérifie pour le cas où les deux arguments (s, d) sont négatifs et un résultat positif. La logique est largement équivalente au cas positif; Le bit de signe sera effacé pour tous les modèles de bits pouvant résulter de l'ajout de deux valeurs négatives si et seulement si, un dépassement de capacité s'est produit.

64
Durandal

Java ne fait rien avec le dépassement d'entier pour les types primitifs int ou long et ignore le dépassement avec les entiers positifs et négatifs. 

Cette réponse décrit d'abord le dépassement d'entier, donne un exemple de la façon dont cela peut se produire, même avec des valeurs intermédiaires dans l'évaluation de l'expression, puis fournit des liens vers des ressources qui fournissent des techniques détaillées pour la prévention et la détection du dépassement d'entier. 

L'arithmétique entière et les expressions renvoyant à un dépassement inattendu ou non détecté constituent une erreur de programmation courante. Un dépassement d'entier inattendu ou non détecté est également un problème de sécurité exploitable bien connu, en particulier dans la mesure où il affecte des objets tableau, pile et liste. 

Le débordement peut se produire dans un sens positif ou négatif, la valeur positive ou négative dépassant les valeurs maximales ou minimales pour le type de primitive en question. Un dépassement de capacité peut survenir dans une valeur intermédiaire lors de l'évaluation d'une expression ou d'une opération et affecter le résultat d'une expression ou d'une opération dans laquelle la valeur finale devrait être comprise dans la plage. 

Parfois, un débordement négatif est appelé par inadvertance un débordement. Le dépassement est ce qui se produit lorsqu'une valeur est plus proche de zéro que ne le permet la représentation. Un débordement se produit en arithmétique entière et est attendu. Un dépassement de nombre entier se produit lorsqu'une évaluation entière est comprise entre -1 et 0 ou 0 et 1. Ce qui serait un résultat fractionnaire est tronqué à 0. Ceci est normal et attendu avec l'arithmétique entière et n'est pas considéré comme une erreur. Cependant, cela peut conduire à une exception du code. Un exemple est une exception "ArithmeticException:/by zero" si le résultat du dépassement entier est utilisé comme diviseur dans une expression. 

Considérons le code suivant:

int bigValue = Integer.MAX_VALUE;
int x = bigValue * 2 / 5;
int y = bigValue / x;

x étant attribué à x et l'évaluation ultérieure de bigValue/x lève une exception, "ArithmeticException:/by zero" (c'est-à-dire diviser par zéro), au lieu que y soit affectée de la valeur 2. 

Le résultat attendu pour x serait de 858 993 458, ce qui est inférieur à la valeur int maximale de 2 147 483 647. Cependant, le résultat intermédiaire de l'évaluation de Integer.MAX_Value * 2 serait de 4 294 967 294, ce qui dépasse la valeur maximale de int et est égal à -2 conformément aux représentations des entiers complémentaires à 2s. L'évaluation ultérieure de -2/5 est évaluée à 0, ce qui est attribué à x. 

Réorganisation de l'expression permettant de calculer x en une expression qui, une fois évaluée, divise avant de multiplier le code suivant:

int bigValue = Integer.MAX_VALUE;
int x = bigValue / 5 * 2;
int y = bigValue / x;

x8 se voit attribuer 858 993 458 et y, 2, ce qui est attendu.

Le résultat intermédiaire de bigValue/5 est 429 496 729 et ne dépasse pas la valeur maximale pour un int. Une évaluation ultérieure de 429 496 729 * 2 ne dépasse pas la valeur maximale pour un int et le résultat attendu est attribué à x. L'évaluation pour y ne se divise pas par zéro. Les évaluations pour x et y fonctionnent comme prévu.

Les valeurs entières Java sont stockées sous la forme et se comportent conformément aux représentations des entiers signés du complément à 2. Lorsqu'une valeur résultante serait plus grande ou plus petite que les valeurs entières maximum ou minimum, il en résulte une valeur entière de complément à 2. Dans les situations qui ne sont pas expressément conçues pour utiliser le comportement du complément à 2, qui correspond à la plupart des situations arithmétiques entières ordinaires, la valeur du complément à 2 résultante provoquera une erreur de logique de programmation ou de calcul, comme indiqué dans l'exemple ci-dessus. Un excellent article Wikipedia décrit ici les entiers binaires du complément 2: Complément à deux - Wikipedia

Il existe des techniques pour éviter les débordements entiers non intentionnels. Les technologies peuvent être classées comme utilisant les tests de précondition, la conversion ascendante et BigInteger. 

Le test des conditions préalables consiste à examiner les valeurs entrant dans une opération ou une expression arithmétique afin de s’assurer qu’il n’y aura pas de débordement avec ces valeurs. La programmation et la conception devront créer des tests garantissant que les valeurs en entrée ne causeront pas de débordement, puis déterminer quoi faire si des valeurs en entrée entraînent un débordement. 

La diffusion ascendante consiste à utiliser un type primitif plus grand pour effectuer l'opération ou l'expression arithmétique, puis à déterminer si la valeur résultante est supérieure aux valeurs maximale ou minimale d'un entier. Même avec la conversion ascendante, il est toujours possible que la valeur ou une valeur intermédiaire dans une opération ou une expression dépasse les valeurs maximales ou minimales pour le type de diffusion ascendante et provoque un dépassement de capacité, qui ne sera pas non plus détecté et provoquera des résultats inattendus et indésirables. Par le biais d’analyses ou de conditions préalables, il peut être possible d’empêcher le débordement avec la conversion ascendante lorsque la prévention sans conversion ascendante n’est ni possible ni pratique. Si les entiers en question sont déjà de longs types primitifs, la conversion ascendante n’est pas possible avec les types primitifs en Java.La technique BigInteger consiste à utiliser BigInteger pour l'opération ou l'expression arithmétique à l'aide de méthodes de bibliothèque utilisant BigInteger. BigInteger ne déborde pas. Il utilisera toute la mémoire disponible, si nécessaire. Ses méthodes arithmétiques ne sont normalement que légèrement moins efficaces que les opérations sur les nombres entiers. Il est toujours possible qu'un résultat utilisant BigInteger dépasse les valeurs maximale ou minimale d'un entier. Toutefois, aucun dépassement de capacité ne surviendra dans l'arithmétique menant au résultat. La programmation et la conception devront toujours déterminer quoi faire si un résultat BigInteger dépasse les valeurs maximale ou minimale du type de résultat primitif souhaité, par exemple, int ou long.

Le programme CERT du Carnegie Mellon Software Engineering Institute et Oracle ont créé un ensemble de normes pour une programmation Java sécurisée. Les normes incluent des techniques de prévention et de détection des débordements d’entiers. La norme est publiée en tant que ressource en ligne librement accessible ici: CERT Oracle Standard Coding Secure pour Java

La section de la norme qui décrit et contient des exemples pratiques de techniques de codage pour prévenir ou détecter les dépassements d’entier est disponible: NUM00-J. Détecter ou empêcher le dépassement d'entier

Les formulaires de livre et PDF de la norme CERT Oracle Secure Coding Standard pour Java sont également disponibles.

Book form and PDF form of The CERT Oracle Secure Coding Standard for Java are also available.

31
Jim

Ayant un peu rencontré ce problème moi-même, voici ma solution (pour la multiplication et l'addition):

static boolean wouldOverflowOccurwhenMultiplying(int a, int b) {
    // If either a or b are Integer.MIN_VALUE, then multiplying by anything other than 0 or 1 will result in overflow
    if (a == 0 || b == 0) {
        return false;
    } else if (a > 0 && b > 0) { // both positive, non zero
        return a > Integer.MAX_VALUE / b;
    } else if (b < 0 && a < 0) { // both negative, non zero
        return a < Integer.MAX_VALUE / b;
    } else { // exactly one of a,b is negative and one is positive, neither are zero
        if (b > 0) { // this last if statements protects against Integer.MIN_VALUE / -1, which in itself causes overflow.
            return a < Integer.MIN_VALUE / b;
        } else { // a > 0
            return b < Integer.MIN_VALUE / a;
        }
    }
}

boolean wouldOverflowOccurWhenAdding(int a, int b) {
    if (a > 0 && b > 0) {
        return a > Integer.MAX_VALUE - b;
    } else if (a < 0 && b < 0) {
        return a < Integer.MIN_VALUE - b;
    }
    return false;
}

n'hésitez pas à corriger si mal ou si peut être simplifié. J'ai fait quelques tests avec la méthode de multiplication, principalement des cas Edge, mais cela pourrait toujours être faux.

11
fragorl

Certaines bibliothèques fournissent des opérations arithmétiques sûres, qui vérifient les débordements/dépassements d'entiers. Par exemple, IntMath.checkedAdd (int a, int b) de Guava retourne la somme de a et b, à condition qu'elle ne déborde pas et jette ArithmeticException si a + b déborde dans l'arithmétique signée int.

8
reprogrammer

Ça tourne autour.

par exemple:

public class Test {

    public static void main(String[] args) {
        int i = Integer.MAX_VALUE;
        int j = Integer.MIN_VALUE;

        System.out.println(i+1);
        System.out.println(j-1);
    }
}

empreintes

-2147483648
2147483647
6
Peter Tillemans

Je pense que vous devriez utiliser quelque chose comme ceci et cela s'appelle Upcasting:

public int multiplyBy2(int x) throws ArithmeticException {
    long result = 2 * (long) x;    
    if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE){
        throw new ArithmeticException("Integer overflow");
    }
    return (int) result;
}

Vous pouvez lire plus loin ici: Détecter ou empêcher le dépassement d’entier

C'est une source assez fiable.

5
Dusan

Cela ne fait rien - le sous/débordement se produit.

Un "-1" résultant d'un calcul de débordement n'est pas différent du "-1" résultant de toute autre information. Donc, vous ne pouvez pas dire via un statut ou en inspectant simplement une valeur si elle est débordée.

Mais vous pouvez faire preuve de discernement dans vos calculs pour éviter les débordements, si cela compte, ou au moins savoir quand cela se produira. Quelle est ta situation?

3
Sean Owen

Il y a un cas, qui n'est pas mentionné ci-dessus:

int res = 1;
while (res != 0) {
    res *= 2;

}
System.out.println(res);

produira:

0

Ce cas a été discuté ici: Un débordement d’entier produit zéro

0
lobzik
static final int safeAdd(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left > Integer.MAX_VALUE - right
                : left < Integer.MIN_VALUE - right) {
    throw new ArithmeticException("Integer overflow");
  }
  return left + right;
}

static final int safeSubtract(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left < Integer.MIN_VALUE + right
                : left > Integer.MAX_VALUE + right) {
    throw new ArithmeticException("Integer overflow");
  }
  return left - right;
}

static final int safeMultiply(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left > Integer.MAX_VALUE/right
                  || left < Integer.MIN_VALUE/right
                : (right < -1 ? left > Integer.MIN_VALUE/right
                                || left < Integer.MAX_VALUE/right
                              : right == -1
                                && left == Integer.MIN_VALUE) ) {
    throw new ArithmeticException("Integer overflow");
  }
  return left * right;
}

static final int safeDivide(int left, int right)
                 throws ArithmeticException {
  if ((left == Integer.MIN_VALUE) && (right == -1)) {
    throw new ArithmeticException("Integer overflow");
  }
  return left / right;
}

static final int safeNegate(int a) throws ArithmeticException {
  if (a == Integer.MIN_VALUE) {
    throw new ArithmeticException("Integer overflow");
  }
  return -a;
}
static final int safeAbs(int a) throws ArithmeticException {
  if (a == Integer.MIN_VALUE) {
    throw new ArithmeticException("Integer overflow");
  }
  return Math.abs(a);
}
0
user4267316

Je pense que ça devrait aller.

static boolean addWillOverFlow(int a, int b) {
    return (Integer.signum(a) == Integer.signum(b)) && 
            (Integer.signum(a) != Integer.signum(a+b)); 
}
0
John Woo