¿Por qué int i = 1024 * 1024 * 1024 * 1024 se compila sin error?

152

El límite de intes de -2147483648 a 2147483647.

Si ingreso

int i = 2147483648;

entonces Eclipse mostrará un subrayado rojo debajo de "2147483648".

Pero si hago esto:

int i = 1024 * 1024 * 1024 * 1024;

Se compilará bien.

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

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

    }
}

Tal vez es una pregunta básica en Java, pero no tengo idea de por qué la segunda variante no produce ningún error.

WUJ
fuente
10
Incluso si el compilador normalmente "colapsaría" el cálculo en un solo valor como optimización, no lo hará si el resultado fuera un desbordamiento, ya que ninguna optimización debería cambiar el comportamiento del programa.
Hot Licks
1
Y no puede interpretar 2147483648: este literal no tiene sentido.
Denys Séguret
1
Y Java no informa desbordamientos de enteros: la operación "falla" en silencio.
Hot Licks
55
@JacobKrall: C # informará esto como un defecto, independientemente de si la verificación está activada; todos los cálculos que consisten en solo expresiones constantes se verifican automáticamente a menos que estén dentro de una región sin marcar.
Eric Lippert
54
Te desanimo de hacer preguntas de "por qué no" en StackOverflow; Son difíciles de responder. Una pregunta de "por qué no" presupone que el mundo obviamente debería ser así, y que debe haber una buena razón para que sea así. Esta suposición casi nunca es válida. Una pregunta más precisa sería algo así como "¿qué sección de la especificación describe cómo se calcula la aritmética de enteros constantes?" o "¿cómo se manejan los desbordamientos de enteros en Java?"
Eric Lippert

Respuestas:

233

No hay nada malo con esa declaración; solo estás multiplicando 4 números y asignándolos a un int, simplemente sucede que hay un desbordamiento. Esto es diferente de asignar un solo literal , que se verificaría en los límites en tiempo de compilación.

Es el literal fuera de límites lo que causa el error, no la asignación :

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

Por el contrario, un longliteral compilaría bien:

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

Tenga en cuenta que, de hecho, el resultado es todavía calcula en tiempo de compilación, porque 1024 * 1024 * 1024 * 1024es una expresión constante :

int i = 1024 * 1024 * 1024 * 1024;

se convierte en:

   0: iconst_0      
   1: istore_1      

Tenga en cuenta que el resultado ( 0) simplemente se carga y almacena, y no se produce ninguna multiplicación.


De JLS §3.10.1 (gracias a @ChrisK por mencionarlo en los comentarios):

Es un error en tiempo de compilación si un literal decimal de tipo intes mayor que 2147483648(2 31 ), o si el literal decimal 2147483648aparece en cualquier otro lugar que no sea el operando del operador menos unario ( §15.15.4 ).

arshajii
fuente
12
Y para la multiplicación, el JLS dice: Si una multiplicación entera se desborda, entonces el resultado son los bits de orden inferior del producto matemático como se representa en un formato de complemento a dos suficientemente grande. Como resultado, si se produce un desbordamiento, entonces el signo del resultado puede no ser el mismo que el signo del producto matemático de los dos valores de operando.
Chris K
3
Excelente respuesta Algunas personas parecen tener la impresión de que el desbordamiento es algún tipo de error o falla, pero no lo es.
Wouter Lievens
3
@ iowatiger08 La semántica del lenguaje se describe en JLS, que es independiente de la JVM (por lo que no debería importar qué JVM utilice).
arshajii
44
@WouterLievens, el desbordamiento es normalmente una condición "inusual", si no una condición de error absoluto. Es el resultado de las matemáticas de precisión finita, que la mayoría de la gente no espera intuitivamente que sucedan cuando hacen matemáticas. En algunos casos, como -1 + 1, es inofensivo; pero 1024^4podría sorprender a las personas con resultados totalmente inesperados, lejos de lo que esperarían ver. Creo que debería haber al menos una advertencia o nota para el usuario, y no ignorarlo en silencio.
Phil Perry
1
@ iowatiger08: el tamaño de int es fijo; sí no depende de la JVM. Java no es C.
Martin Schröder
43

1024 * 1024 * 1024 * 1024y 2147483648no tienen el mismo valor en Java.

En realidad, 2147483648 NO ES UN VALOR (aunque lo 2147483648Lsea) en Java. El compilador literalmente no sabe qué es ni cómo usarlo. Entonces se queja.

1024es un int válido en Java, y un válido intmultiplicado por otro válido int, siempre es válido int. Incluso si no es el mismo valor que esperaría intuitivamente porque el cálculo se desbordará.

Ejemplo

Considere el siguiente ejemplo de código:

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

¿Esperaría que esto generara un error de compilación? Ahora se vuelve un poco más resbaladizo.
¿Qué pasa si ponemos un ciclo con 3 iteraciones y multiplicado en el ciclo?

El compilador puede optimizar, pero no puede cambiar el comportamiento del programa mientras lo hace.


Alguna información sobre cómo se maneja realmente este caso:

En Java y muchos otros lenguajes, los enteros consistirán en un número fijo de bits. Los cálculos que no caben en el número de bits dado se desbordarán ; el cálculo se realiza básicamente módulo 2 ^ 32 en Java, después de lo cual el valor es de nuevo convertida en un firmado entero.

Otros lenguajes o API utilizan un número dinámico de bits ( BigIntegeren Java), generan una excepción o establecen el valor en un valor mágico como no un número.

Cruncher
fuente
8
Para mí, su declaración, " 2147483648NO ES AUN UN VALOR (aunque lo 2147483648Lsea)", realmente consolidó el punto que @arshajii estaba tratando de hacer.
kdbanman
Ah, lo siento, sí, ese fui yo. Me faltaba la noción desbordamiento / aritmética modular en su respuesta. Tenga en cuenta que puede retroceder si no está de acuerdo con mi edición.
Maarten Bodewes
@owlstead Su edición es objetivamente correcta. Mi razón para no incluirlo fue que: independientemente de cómo 1024 * 1024 * 1024 * 1024se maneje, realmente quería enfatizar que no es lo mismo que escribir 2147473648. Hay muchas formas (y usted ha enumerado algunas) de que un idioma podría tratarlo. Está razonablemente separado y es útil. Entonces lo dejo. Mucha información se hace cada vez más necesaria cuando tienes una respuesta de alto rango en una pregunta popular.
Cruncher
16

No tengo idea de por qué la segunda variante no produce ningún error.

El comportamiento que sugiere, es decir, la producción de un mensaje de diagnóstico cuando un cálculo produce un valor que es mayor que el valor más grande que se puede almacenar en un entero , es una característica . Para que pueda utilizar cualquier función, debe pensar en ella, considerarla una buena idea, diseñar, especificar, implementar, probar, documentar y enviar a los usuarios.

Para Java, una o más de las cosas en esa lista no sucedieron y, por lo tanto, no tiene la función. No se cual; tendrías que preguntarle a un diseñador de Java.

Para C #, todas esas cosas sucedieron, hace unos catorce años, por lo que el programa correspondiente en C # ha producido un error desde C # 1.0.

Eric Lippert
fuente
45
Esto no agrega nada útil. Si bien no me importa tomar puñaladas en Java, no respondió a la pregunta de los OP en absoluto.
Seiyria
29
@Seiyria: El póster original pregunta "¿por qué no?" pregunta: "¿por qué el mundo no es como creo que debería ser?" no es una pregunta técnica precisa sobre el código real y, por lo tanto, es una mala pregunta para StackOverflow. El hecho de que la respuesta correcta a una pregunta vaga y no técnica sea vaga y no técnica no debería sorprender. Animo al cartel original a hacer una mejor pregunta y evitar "¿por qué no?" preguntas
Eric Lippert
18
@Seiyria: La respuesta aceptada que noto tampoco responde a esta pregunta vaga y no técnica; la pregunta es "¿por qué esto no es un error?" y la respuesta aceptada es "porque es legal". Esto es simplemente reafirmar la pregunta ; respondiendo "¿por qué el cielo no es verde?" con "porque es azul" no responde la pregunta. Pero como la pregunta es mala, no culpo en absoluto al que responde; La respuesta es una respuesta perfectamente razonable a una pregunta pobre.
Eric Lippert
13
Sr. Eric, esta es la pregunta que publiqué: "¿Por qué int i = 1024 * 1024 * 1024 * 1024; sin informe de error en eclipse?". y la respuesta de arshajii es exactamente lo que yo (quizás más). A veces no puedo expresar ninguna pregunta de una manera muy precisa. Creo que es por eso que algunas personas modifican algunas preguntas publicadas más precisas en Stackoverflow. Creo que si quiero obtener la respuesta "porque es legal", no publicaré esta pregunta. Haré todo lo posible para publicar algunas "preguntas regulares", pero entienda a alguien como yo que es un estudiante y no tan profesional. Gracias.
WUJ
55
@WUJ Esta respuesta en mi humilde opinión proporciona información y perspectiva adicionales. Después de leer todas las respuestas, encontré que esta respuesta proporcionaba tanta validez como las otras respuestas proporcionadas. También aumenta la conciencia de que los desarrolladores no son los únicos implementadores de algunos productos de software.
SoftwareCarpenter
12

Además de la respuesta de arshajii, quiero mostrar una cosa más:

No es la asignación la que causa el error, sino simplemente el uso del literal . Cuando intentas

long i = 2147483648;

notarás que también causa un error de compilación ya que el lado derecho todavía es un intliteral y está fuera de rango.

Por lo tanto, las operaciones con intvalores (y eso incluye asignaciones) pueden desbordarse sin un error de compilación (y sin un error de tiempo de ejecución también), pero el compilador simplemente no puede manejar esos literales demasiado grandes.

piet.t
fuente
1
Correcto. Asignar un int a un largo incluye un elenco implícito. Pero el valor nunca puede existir como int en primer lugar para ser lanzado :)
Cruncher
4

A: Porque no es un error.

Antecedentes: la multiplicación 1024 * 1024 * 1024 * 1024conducirá a un desbordamiento. Un desbordamiento es muy a menudo un error. Diferentes lenguajes de programación producen un comportamiento diferente cuando se producen desbordamientos. Por ejemplo, C y C ++ lo llaman "comportamiento indefinido" para enteros con signo, y el comportamiento se define como enteros sin signo (tome el resultado matemático, agregue UINT_MAX + 1siempre que el resultado sea negativo, reste UINT_MAX + 1siempre que el resultado sea mayor que UINT_MAX).

En el caso de Java, si el resultado de una operación con intvalores no está en el rango permitido, conceptualmente Java suma o resta 2 ^ 32 hasta que el resultado esté en el rango permitido. Entonces, la declaración es completamente legal y no está equivocada. Simplemente no produce el resultado que esperaba.

Seguramente puede discutir si este comportamiento es útil y si el compilador debería darle una advertencia. Yo personalmente diría que una advertencia sería muy útil, pero un error sería incorrecto ya que es Java legal.

gnasher729
fuente