Pourquoi int i = 1024 * 1024 * 1024 * 1024 se compile sans erreur?

152

La limite de intest comprise entre -2147483648 et 2147483647.

Si j'entre

int i = 2147483648;

puis Eclipse affichera un soulignement rouge sous "2147483648".

Mais si je fais ça:

int i = 1024 * 1024 * 1024 * 1024;

il compilera très bien.

public class Test {
    public static void main(String[] args) {        

        int i = 2147483648;                   // error
        int j = 1024 * 1024 * 1024 * 1024;    // no error

    }
}

C'est peut-être une question basique en Java, mais je n'ai aucune idée de la raison pour laquelle la deuxième variante ne produit aucune erreur.

WUJ
la source
10
Même si le compilateur "réduisait" normalement le calcul en une seule valeur en tant qu'optimisation, il ne le fera pas si le résultat serait un débordement, car aucune optimisation ne devrait changer le comportement du programme.
Hot Licks
1
Et il ne peut pas interpréter 2147483648: ce littéral n'a aucun sens.
Denys Séguret
1
Et Java ne signale pas les débordements d'entiers - l'opération "échoue" silencieusement.
Hot Licks
5
@JacobKrall: C # signalera cela comme un défaut, que la vérification soit activée ou non; tous les calculs constitués uniquement d'expressions constantes sont automatiquement vérifiés, sauf dans une région non cochée.
Eric Lippert
54
Je vous déconseille de poser des questions «pourquoi pas» sur StackOverflow; il est difficile de répondre. Une question «pourquoi pas» présuppose que le monde doit évidemment être une manière dont il n'est pas, et qu'il doit y avoir une bonne raison pour qu'il en soit ainsi. Cette hypothèse n'est presque jamais valable. Une question plus précise serait quelque chose comme "quelle section de la spécification décrit comment l'arithmétique des nombres entiers constants est calculée?" ou "comment les débordements d'entiers sont-ils gérés en Java?"
Eric Lippert

Réponses:

233

Il n'y a rien de mal avec cette déclaration; vous ne faites que multiplier 4 nombres et l'assigner à un entier, il se trouve juste qu'il y a un débordement. Ceci est différent de l'affectation d'un seul littéral , qui serait vérifié par les limites au moment de la compilation.

Il est le hors-limites littérale qui provoque l'erreur, pas la cession :

System.out.println(2147483648);        // error
System.out.println(2147483647 + 1);    // no error

En revanche, un longlittéral compilerait bien:

System.out.println(2147483648L);       // no error

Notez qu'en fait, le résultat est toujours calculé au moment de la compilation car il 1024 * 1024 * 1024 * 1024s'agit d'une expression constante :

int i = 1024 * 1024 * 1024 * 1024;

devient:

   0: iconst_0      
   1: istore_1      

Notez que le résultat ( 0) est simplement chargé et stocké et qu'aucune multiplication n'a lieu.


De JLS §3.10.1 (merci à @ChrisK pour l'avoir soulevé dans les commentaires):

C'est une erreur à la compilation si un littéral décimal de type intest plus grand que 2147483648(2 31 ), ou si le littéral décimal 2147483648apparaît ailleurs que comme l'opérande de l'opérateur unaire moins ( §15.15.4 ).

arshajii
la source
12
Et pour la multiplication, le JLS dit: Si une multiplication entière déborde, alors le résultat est les bits de poids faible du produit mathématique tel que représenté dans un format de complément à deux suffisamment grand. Par conséquent, en cas de dépassement de capacité, le signe du résultat peut ne pas être le même que le signe du produit mathématique des deux valeurs d'opérande.
Chris K
3
Excellente réponse. Certaines personnes semblent avoir l'impression que le débordement est une sorte d'erreur ou d'échec, mais ce n'est pas le cas.
Wouter Lievens
3
@ iowatiger08 La sémantique du langage est définie par le JLS, qui est indépendant de la JVM (donc peu importe la JVM que vous utilisez).
arshajii
4
@WouterLievens, le débordement est normalement une condition "inhabituelle", sinon une condition d'erreur pure et simple. C'est le résultat de mathématiques de précision finie, ce que la plupart des gens ne s'attendent pas intuitivement à se produire lorsqu'ils font des mathématiques. Dans certains cas, comme -1 + 1, c'est inoffensif; mais car 1024^4cela pourrait aveugler les gens avec des résultats totalement inattendus, loin de ce qu'ils s'attendent à voir. Je pense qu'il devrait y avoir au moins un avertissement ou une note à l'utilisateur, et ne pas l'ignorer silencieusement.
Phil Perry
1
@ iowatiger08: La taille de int est fixe; cela ne dépend pas de la JVM. Java n'est pas C.
Martin Schröder
43

1024 * 1024 * 1024 * 1024et 2147483648n'ont pas la même valeur en Java.

En fait, 2147483648 N'EST MÊME PAS UNE VALEUR (bien que ce 2147483648Lsoit le cas) en Java. Le compilateur ne sait littéralement pas ce que c'est, ni comment l'utiliser. Alors ça gémit.

1024est un entier valide en Java, et un valide intmultiplié par un autre valide intest toujours un valide int. Même si ce n'est pas la même valeur que vous attendez intuitivement car le calcul débordera.

Exemple

Considérez l'exemple de code suivant:

public static void main(String[] args) {
    int a = 1024;
    int b = a * a * a * a;
}

Vous attendez-vous à ce que cela génère une erreur de compilation? Cela devient un peu plus glissant maintenant.
Et si on mettait une boucle avec 3 itérations et qu'on multipliait dans la boucle?

Le compilateur est autorisé à optimiser, mais il ne peut pas modifier le comportement du programme pendant ce temps.


Quelques informations sur la manière dont ce cas est réellement traité:

En Java et dans de nombreux autres langages, les entiers seront constitués d'un nombre fixe de bits. Les calculs qui ne correspondent pas au nombre de bits donné déborderont ; le calcul est essentiellement effectué module 2 ^ 32 en Java, après quoi la valeur est reconvertie en un entier signé .

D'autres langages ou API utilisent un nombre dynamique de bits ( BigIntegeren Java), lèvent une exception ou définissent la valeur sur une valeur magique telle que not-a-number.

Cruncher
la source
8
Pour moi, votre déclaration, " 2147483648N'EST MÊME PAS UNE VALEUR (bien qu'elle l' 2147483648Lest)", a vraiment cimenté le point que @arshajii essayait de faire valoir.
kdbanman le
Ah, désolé, oui, c'était moi. Il me manquait la notion de débordement / arithmétique modulaire dans votre réponse. Notez que vous pouvez revenir en arrière si vous n'êtes pas d'accord avec ma modification.
Maarten Bodewes
@owlstead Votre modification est factuellement correcte. La raison pour laquelle 1024 * 1024 * 1024 * 1024je ne l'ai pas inclus était la suivante: quelle que soit la manière dont cela est traité, je voulais vraiment souligner que ce n'est pas la même chose que l'écriture 2147473648. Il existe de nombreuses façons (et vous en avez énuméré quelques-unes) qu'une langue pourrait potentiellement y faire face. C'est raisonnablement séparé et utile. Alors je vais le laisser. De nombreuses informations deviennent de plus en plus nécessaires lorsque vous avez une réponse de premier ordre à une question populaire.
Cruncher
16

Je n'ai aucune idée de la raison pour laquelle la deuxième variante ne produit aucune erreur.

Le comportement que vous suggérez - c'est-à-dire la production d'un message de diagnostic lorsqu'un calcul produit une valeur supérieure à la plus grande valeur pouvant être stockée dans un entier - est une fonctionnalité . Pour que vous puissiez utiliser une fonctionnalité, celle-ci doit être pensée, considérée comme une bonne idée, conçue, spécifiée, implémentée, testée, documentée et livrée aux utilisateurs.

Pour Java, une ou plusieurs des choses de cette liste ne se sont pas produites et vous ne disposez donc pas de la fonctionnalité. Je ne sais pas lequel; vous devrez demander à un concepteur Java.

Pour C #, toutes ces choses se sont produites - il y a environ quatorze ans maintenant - et donc le programme correspondant en C # a produit une erreur depuis C # 1.0.

Eric Lippert
la source
45
Cela n'ajoute rien d'utile. Bien que cela ne me dérange pas d'essayer Java, cela n'a pas du tout répondu à la question des OP.
Seiyria
29
@Seiyria: L'affiche originale demande un "pourquoi pas?" question - "pourquoi le monde n'est-il pas tel que je pense qu'il devrait être?" n'est pas une question technique précise sur le code réel , et c'est donc une mauvaise question pour StackOverflow. Le fait que la réponse correcte à une question vague et non technique soit vague et non technique ne devrait pas être surprenant. J'encourage l'affiche originale à poser une meilleure question et à éviter "pourquoi pas?" des questions.
Eric Lippert
18
@Seiyria: La réponse acceptée que je note ne répond pas non plus à cette question vague et non technique; la question est "pourquoi n'est-ce pas une erreur?" et la réponse acceptée est "parce que c'est légal". C'est simplement reformuler la question ; répondant "pourquoi le ciel n'est-il pas vert?" avec "parce que c'est bleu" ne répond pas à la question. Mais puisque la question est une mauvaise question, je ne blâme pas du tout le répondant; la réponse est une réponse parfaitement raisonnable à une mauvaise question.
Eric Lippert
13
M. Eric, C'est la question que j'ai postée: "Pourquoi int i = 1024 * 1024 * 1024 * 1024; sans rapport d'erreur dans eclipse?". et la réponse de arshajii est exactement ce que je quoi (peut-être plus). Parfois, je ne peux pas exprimer de questions de manière très précise. Je pense que c'est pourquoi certaines personnes modifient certaines questions publiées plus précisément dans Stackoverflow. Je pense que si je veux obtenir la réponse "parce que c'est légal", je ne posterai pas cette question. Je ferai de mon mieux pour publier des "questions régulières", mais s'il vous plaît, comprenez quelqu'un comme moi qui est étudiant et pas si professionnel. Merci.
WUJ
5
@WUJ Cette réponse IMHO fournit un aperçu et une perspective supplémentaires. Après avoir lu toutes les réponses, j'ai trouvé que cette réponse fournissait autant de validité que les autres réponses fournies. Cela permet également de prendre conscience que les développeurs ne sont pas les seuls à mettre en œuvre certains produits logiciels.
SoftwareCarpenter
12

En plus de la réponse d'Arshajii, je veux montrer une dernière chose:

Ce n'est pas l' affectation qui cause l'erreur mais simplement l'utilisation du littéral . Quand tu essaies

long i = 2147483648;

vous remarquerez que cela provoque également une erreur de compilation puisque le côté droit est toujours un int-literal et hors de portée.

Ainsi, les opérations avec int-values ​​(et cela inclut les affectations) peuvent déborder sans erreur de compilation (et sans erreur d'exécution également), mais le compilateur ne peut tout simplement pas gérer ces littéraux trop gros.

piet.t
la source
1
Droite. L'affectation d'un int à un long inclut un cast implicite. Mais la valeur ne peut jamais exister en tant que int en premier lieu pour être casté :)
Cruncher
4

R: Parce que ce n'est pas une erreur.

Contexte: La multiplication 1024 * 1024 * 1024 * 1024entraînera un débordement. Un débordement est très souvent un bug. Différents langages de programmation produisent un comportement différent lorsque des débordements se produisent. Par exemple, C et C ++ l'appellent «comportement indéfini» pour les entiers signés, et le comportement est défini sur les entiers non signés (prenez le résultat mathématique, ajoutez UINT_MAX + 1tant que le résultat est négatif, soustrayez UINT_MAX + 1tant que le résultat est supérieur à UINT_MAX).

Dans le cas de Java, si le résultat d'une opération avec des intvaleurs n'est pas dans la plage autorisée, Java ajoute ou soustrait conceptuellement 2 ^ 32 jusqu'à ce que le résultat soit dans la plage autorisée. La déclaration est donc tout à fait légale et non erronée. Cela ne produit tout simplement pas le résultat que vous espériez peut-être.

Vous pouvez sûrement vous demander si ce comportement est utile et si le compilateur doit vous avertir. Je dirais personnellement qu'un avertissement serait très utile, mais une erreur serait incorrecte car il s'agit de Java légal.

gnasher729
la source