Comment Java gère-t-il les sous-stream et débordements d’entiers et comment les vérifier?

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

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

S’il déborde, il retourne à la valeur minimale et continue à partir de là. S’il est sous-estimé, il revient à 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 long )

Si vous pensez que cela peut se produire plus que souvent, alors envisagez d'utiliser un type de données ou un object pouvant stocker des valeurs plus importantes, par exemple long ou peut-être java.math.BigInteger . Le dernier ne déborde pas, en pratique, la mémoire JVM disponible est la limite.


Si vous vous trouvez déjà sur Java8, vous pouvez utiliser les nouvelles Math#addExact() et Math#subtractExact() qui lancent une ArithmeticException lors du débordement.

 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 aussi les utiliser tout de suite au lieu de les cacher dans une méthode d’utilisation boolean .

Eh bien, en ce qui concerne les types entiers primitifs, Java ne gère pas du tout le dépassement / débordement (pour le flottant et le double, le comportement est différent, il se déplacera à +/- infini comme les mandats 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 suivant pour effectuer 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 projection dépend des exigences de votre application (jeter, rincer à min / max ou simplement enregistrer quoi que ce soit). Si vous souhaitez détecter un dépassement de capacité lors d'opérations longues, vous n'avez pas de chance avec les primitives, utilisez plutôt BigInteger.


Edit (21/05/2014): Puisque cette question semble être assez fréquemment évoquée et que j'ai dû résoudre le même problème moi-même, il est assez facile d'évaluer la condition de débordement par la même méthode qu'un CPU calculera son drapeau V.

C'est essentiellement 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; } 

En Java, il est plus simple d'appliquer l'expression (dans le if) aux 32 bits entiers, et de vérifier le résultat en utilisant <0 (cela testera efficacement le bit de signe). Le principe fonctionne exactement de la même manière pour tous les types primitifs de nombres entiers , en changeant toutes les déclarations de la méthode ci-dessus pour les faire fonctionner longtemps.

Pour les types plus petits, en raison de la conversion implicite en int (voir les opérations JLS pour les opérations binarys pour plus de détails), au lieu de vérifier <0, la vérification doit masquer explicitement le bit de signe (0x8000 pour les opérandes courts, 0x80 pour les opérandes octets) et déclaration de paramètre 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 besoin de soustraire la détection de débordement)


Alors, comment / pourquoi ces expressions booléennes fonctionnent-elles? Tout d'abord, certaines reflections logiques révèlent qu'un débordement ne peut se produire que si les signes des deux arguments sont les mêmes. Parce que si un argument est négatif et un autre positif, le résultat (de add) doit être plus proche de zéro, ou dans le cas extrême, un argument est zéro, le même que l’autre argument. Étant donné que les arguments eux-mêmes ne peuvent pas créer une condition de dépassement, leur sum ne peut pas non plus créer de débordement.

Alors, que se passe-t-il si les deux arguments ont le même signe? Jetons un coup d'oeil au cas où les deux sont positifs: l'ajout de deux arguments qui créent une sum plus grande que les types MAX_VALUE, donnera toujours une valeur négative, donc un débordement se produit si arg1 + arg2> MAX_VALUE. Maintenant, la valeur maximale qui pourrait en résulter serait MAX_VALUE + MAX_VALUE (le cas extrême où les deux arguments sont MAX_VALUE). Pour un octet (exemple) qui signifierait 127 + 127 = 254. En regardant les représentations binarys de toutes les valeurs pouvant résulter de l'ajout de deux valeurs positives, on constate que celles qui débordent (128 à 254) ont toutes un bit 7, tandis que tout ce qui ne déborde pas (0 à 127) a le bit 7 (en haut, signe) effacé. C'est exactement ce que la première partie (droite) 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 tous les 32 bits, mais le seul bit qui nous intéresse est le bit le plus haut (signe), contrôlé par le <0).

Maintenant, si les deux arguments sont négatifs, leur sum ne peut jamais être plus proche de zéro que l'un des arguments, la sum doit être plus proche de moins l'infini. La valeur la plus extrême que nous pouvons produire est MIN_VALUE + MIN_VALUE, qui (encore une fois pour un exemple d'octet) montre que pour toute valeur de plage (-1 à -128), le bit de signe est défini, tandis que toute valeur débordante éventuelle (-129 à -256) ) a le bit de signe effacé. Ainsi, le signe du résultat révèle à nouveau la condition de débordement. C'est ce que vérifie la moitié gauche (s & d & r) 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; Tous les modèles de bits pouvant résulter de l'ajout de deux valeurs négatives auront le bit de signe effacé si et seulement si un dépassement de capacité s'est produit.

Java ne fait rien avec un débordement d’entier pour les types primitifs int ou longs et ignore les dépassements avec les entiers positifs et négatifs.

Cette réponse décrit d’abord le débordement d’entier, donne un exemple de la façon dont cela peut se produire, même avec des valeurs intermédiaires dans l’évaluation des expressions, puis donne des liens vers des ressources qui fournissent des techniques détaillées pour prévenir et détecter les dépassements d’entiers.

L’arithmétique entière et les expressions renvoyant à un débordement inattendu ou non détecté sont une erreur de programmation courante. Un débordement 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 les objects array, stack et list.

Le débordement peut se produire dans une direction positive ou négative, la valeur positive ou négative dépassant les valeurs maximales ou minimales du type primitif en question. Un dépassement de capacité peut se produire dans une valeur intermédiaire lors de l’évaluation d’une expression ou d’une opération et avoir une incidence sur le résultat d’une expression ou d’une opération où la valeur finale devrait être comprise dans la plage.

Parfois, le débordement négatif est appelé par erreur sous-dépassement. Le sous-dépassement est ce qui se produit lorsqu’une valeur est plus proche de zéro que ne le permet la représentation. Le débordement se produit dans l’arithmétique entière et est attendu. Un dépassement d’entier se produit lorsqu’une évaluation entière serait comprise entre -1 et 0 ou 0 et 1. Ce qui serait un résultat fractionnaire 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 à un code renvoyant une exception. Un exemple est une exception “ArithmeticException: / by zero” si le résultat du sous-dépassement d’entier est utilisé comme diviseur dans une expression.

Considérez le code suivant:

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

ce qui fait que x est assigné à 0 et que l’évaluation ultérieure de bigValue / x génère une exception, “ArithmeticException: / by zero” (c.-à-d. divise par zéro), au lieu que y soit affecté à la valeur 2.

Le résultat attendu pour x serait 858 993 458, ce qui est inférieur à la valeur maximale de 2 147 483 647. Cependant, le résultat intermédiaire de l’évaluation de Integer.MAX_Value * 2 serait 4 294 967 294, ce qui dépasse la valeur maximale de int et -2 correspond à 2s complémentent les représentations d’entier. L’évaluation ultérieure de -2 / 5 est évaluée à 0 qui est assignée à x.

Réorganiser l’expression pour calculer x à une expression qui, lorsqu’elle est évaluée, divise avant de multiplier, le code suivant:

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

donne comme résultat que x est affecté à 858 993 458 et que y est affecté à 2, ce qui est attendu.

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

Les valeurs entières Java sont stockées en tant que et se comportent conformément aux représentations en nombre entier signées par complément à 2s. Lorsqu’une valeur résultante serait plus grande ou plus petite que les valeurs entières maximales ou minimales, un 2 complèterait plutôt les résultats en valeurs entières. Dans les situations qui ne sont pas expressément conçues pour utiliser le comportement du complément 2, qui est la plupart des situations arithmétiques entières, la valeur de complément 2 résultante provoquera une erreur de programmation ou une erreur de calcul comme le montre l’exemple ci-dessus. Un excellent article de Wikipedia décrit deux compléments binarys entiers ici: complément à deux – Wikipedia

Il existe des techniques pour éviter le débordement d’entier involontaire. Les techinques peuvent être classées dans la catégorie des tests de pré-condition, mise à niveau et BigInteger.

Le test de pré-condition consiste à examiner les valeurs entrant dans une opération ou une expression arithmétique pour s’assurer qu’un débordement ne se produira pas avec ces valeurs. La programmation et la conception devront créer des tests garantissant que les valeurs d’entrée ne provoqueront pas de débordement, puis déterminer ce que vous devez faire si des valeurs d’entrée se produisent et provoquer un débordement.

La mise à jour 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 au-delà des valeurs maximales ou minimales d’un entier. Même avec la remontée, il est toujours possible que la valeur ou une valeur intermédiaire d’une opération ou d’une expression dépasse les valeurs maximales ou minimales du type d’upcast et provoque un débordement, qui ne sera pas détecté et entraînera des résultats inattendus. Grâce à des parsings ou à des conditions préalables, il est possible d’éviter les débordements en cas de surenchère lorsque la prévention sans mise à niveau n’est pas possible ou pratique. Si les entiers en question sont déjà des types primitifs longs, la remontée 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 entières. Il est toujours possible qu’un résultat utilisant BigInteger soit au-delà des valeurs maximales ou minimales d’un entier, cependant, le débordement ne se produira pas 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 maximales ou minimales du type de résultat primitif souhaité, par exemple, int ou long.

Le programme CERT du programme Carnegie Mellon Software Engineering Institute et Oracle ont créé un ensemble de normes pour la programmation Java sécurisée. Les techniques comprennent des techniques pour prévenir et détecter les dépassements d’entier. La norme est publiée en tant que ressource en ligne librement accessible ici: Le standard de codage CERT Oracle Secure pour Java

La section standard qui décrit et contient des exemples pratiques de techniques de codage pour prévenir ou détecter les dépassements d’entiers est ici: NUM00-J. Détecter ou empêcher le débordement d’entier

Le formulaire de réservation et le format PDF de CERT Oracle Secure Coding Standard pour Java sont également disponibles.

Par défaut, les calculs int et long de Java se déroulent silencieusement en cas de dépassement de capacité et de débordement. (Les opérations entières sur d’autres types d’entiers sont effectuées en favorisant d’abord les opérandes à int ou long, conformément à JLS 4.2.2 .)

À partir de Java 8, java.lang.Math fournit les méthodes statiques addExact , subtractExact , multiplyExact , incrementExact , negateExact et negateExact pour les arguments int et long qui exécutent l’opération nommée, en lançant ArithmeticException en cas de dépassement. (Il n’y a pas de méthode divideExact – vous devrez vérifier vous-même le cas particulier ( MIN_VALUE / -1 ).)

À partir de Java 8, java.lang.Math fournit également toIntExact pour lancer un long vers un int, en lançant ArithmeticException si la valeur du long ne correspond pas à un int. Cela peut être utile, par exemple, pour calculer la sum des ints en utilisant des calculs longs non contrôlés, puis en utilisant toIntExact pour le toIntExact en int à la fin (mais faites attention à ne pas laisser votre sum déborder).

Si vous utilisez toujours une version antérieure de Java, Google Guava fournit des méthodes statiques IntMath et LongMath pour les additions, soustractions, multiplications et exponentiations vérifiées (lancement en cas de dépassement). Ces classes fournissent également des méthodes pour calculer les factorielles et les coefficients binomiaux qui renvoient MAX_VALUE en cas de dépassement (ce qui est moins pratique à vérifier). Les classes d’utilitaires primitives de Guava, SignedBytes , UnsignedBytes , Shorts et Ints , fournissent des méthodes checkedCast pour restreindre les plus grands types (lançant une exception IllegalArgumentException sous / débordement, pas une exception ArithmeticException),

Ayant moi-même rencontré ce problème, voici ma solution (à la fois pour la multiplication et l’ajout):

 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 vous avez tort ou si cela peut être simplifié. J'ai fait des tests avec la méthode de multiplication, principalement des cas marginaux, mais cela pourrait toujours être faux.

Il y a des bibliothèques qui fournissent des opérations arithmétiques sûres, qui vérifient le dépassement / dépassement de nombre entier. Par exemple, IntMath.checkedAdd de Guava (int a, int b) retourne la sum de a et b , à condition qu’il ne déborde pas, et lance ArithmeticException si a + b déborde dans l’arithmétique int signé.

Je pense que vous devriez utiliser quelque chose comme ça et ça 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ébordement d'entier

C'est une source assez fiable.

Ça fait le tour.

par exemple:

test de classe publique {

 public static void main(Ssortingng[] args) { int i = Integer.MAX_VALUE; int j = Integer.MIN_VALUE; System.out.println(i+1); System.out.println(j-1); } 

}

estampes

-2147483648

2147483647

Il ne fait rien – le sous / débordement se produit simplement.

Un “-1” qui est le résultat d’un calcul que le 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 juste une valeur si elle est débordée.

Mais vous pouvez être intelligent en ce qui concerne vos calculs afin d’éviter les débordements, si cela est important, ou au moins de savoir quand cela se produira. Quelle est votre situation?

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

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: Le dépassement d’entier produit zéro.

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