Por que int i = 1024 * 1024 * 1024 * 1024 compila sem erro?

152

O limite de inté de -2147483648 a 2147483647.

Se eu inserir

int i = 2147483648;

o Eclipse solicitará um sublinhado vermelho em "2147483648".

Mas se eu fizer isso:

int i = 1024 * 1024 * 1024 * 1024;

compilará bem.

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

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

    }
}

Talvez seja uma pergunta básica em Java, mas não tenho idéia por que a segunda variante não produz erros.

WUJ
fonte
10
Mesmo se o compilador normalmente "recolher" a computação em um único valor como uma otimização, ele não fará isso se o resultado for um estouro, pois nenhuma otimização deve alterar o comportamento do programa.
Hot Licks
1
E não pode interpretar 2147483648: esse literal não faz sentido.
Denys Séguret
1
E o Java não relata estouros inteiros - a operação "falha" silenciosamente.
Hot Licks
5
@JacobKrall: C # relatará isso como um defeito, independentemente de a verificação estar ativada; todos os cálculos que consistem apenas em expressões constantes são verificados automaticamente, a menos que dentro de uma região não verificada.
Eric Lippert
54
Eu desencorajo você a fazer perguntas "por que não" no StackOverflow; eles são difíceis de responder. Uma pergunta do "por que não" pressupõe que o mundo obviamente deva ser um caminho que não é, e que deve haver uma boa razão para que seja assim. Essa suposição quase nunca é válida. Uma pergunta mais precisa seria algo como "que seção da especificação descreve como a aritmética de número inteiro constante é calculada?" ou "como os estouros de número inteiro são tratados em Java?"
Eric Lippert

Respostas:

233

Não há nada de errado com essa afirmação; você está apenas multiplicando 4 números e atribuindo-o a um int, acontece que existe um estouro. Isso é diferente de atribuir um único literal , que seria verificado nos limites em tempo de compilação.

É o literal fora dos limites que causa o erro, não a atribuição :

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

Por outro lado, um longliteral compilaria bem:

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

Observe que, de fato, o resultado ainda é calculado em tempo de compilação porque 1024 * 1024 * 1024 * 1024é uma expressão constante :

int i = 1024 * 1024 * 1024 * 1024;

torna-se:

   0: iconst_0      
   1: istore_1      

Observe que o resultado ( 0) é simplesmente carregado e armazenado, e nenhuma multiplicação ocorre.


Do JLS §3.10.1 (obrigado ao @ChrisK por mencioná-lo nos comentários):

É um erro em tempo de compilação se um literal decimal do tipo intfor maior que 2147483648(2 31 ) ou se o literal decimal 2147483648aparecer em outro local que não seja o operando do operador menos unário ( §15.15.4 ).

arshajii
fonte
12
E para a multiplicação, o JLS diz: Se uma multiplicação de números inteiros exceder o limite, o resultado será os bits de ordem inferior do produto matemático, representados em algum formato de complemento de dois suficientemente grande. Como resultado, se ocorrer um estouro, o sinal do resultado pode não ser o mesmo que o sinal do produto matemático dos dois valores do operando.
Chris K
3
Excelente resposta. Algumas pessoas parecem ter a impressão de que o excesso é algum tipo de erro ou falha, mas não é.
Wouter Lievens
3
@ iowatiger08 A semântica do idioma é descrita pela JLS, que é independente da JVM (portanto, não importa qual JVM você usa).
arshajii
4
@WouterLievens, o estouro é normalmente uma condição "incomum", se não uma condição de erro definitivo. É o resultado da matemática de precisão finita, que a maioria das pessoas não espera intuitivamente que aconteça quando faz matemática. Em alguns casos, como -1 + 1, é inofensivo; mas 1024^4isso poderia deixar de lado as pessoas com resultados totalmente inesperados, longe do que eles esperariam ver. Eu acho que deveria haver pelo menos um aviso ou nota para o usuário, e não ignorá-lo silenciosamente.
Phil Perry
1
@ iowatiger08: O tamanho de int é fixo; isso não depende da JVM. Java não é C.
Martin Schröder
43

1024 * 1024 * 1024 * 1024 e 2147483648 não tem o mesmo valor em Java.

Na verdade, 2147483648 NÃO É UM VALOR (embora2147483648L seja) em Java. O compilador literalmente não sabe o que é ou como usá-lo. Então, lamenta.

1024é um int válido em Java e um válido intmultiplicado por outro válido int, é sempre um válidoint . Mesmo que não seja o mesmo valor que você esperaria intuitivamente, porque o cálculo transbordará.

Exemplo

Considere o seguinte exemplo de código:

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

Você esperaria que isso gerasse um erro de compilação? Torna-se um pouco mais escorregadio agora.
E se colocarmos um loop com 3 iterações e multiplicarmos no loop?

O compilador pode otimizar, mas não pode alterar o comportamento do programa enquanto o faz.


Algumas informações sobre como esse caso é realmente tratado:

Em Java e em muitas outras linguagens, os números inteiros consistem em um número fixo de bits. Cálculos que não cabem no número especificado de bits serão excedidos ; o cálculo é basicamente executado no módulo 2 ^ 32 em Java, após o qual o valor é convertido novamente em um número inteiro assinado .

Outros idiomas ou APIs usam um número dinâmico de bits (BigInteger em Java), geram uma exceção ou configuram o valor para um valor mágico, como não um número.

Triturador
fonte
8
Para mim, sua afirmação, " 2147483648NÃO É MESMO VALOR (embora 2147483648Lseja)", realmente consolidou o argumento que @arshajii estava tentando fazer.
Kdbanman
Ah, desculpe, sim, fui eu. Estava faltando a noção de overflow / aritmética modular em sua resposta. Observe que você pode reverter se não concordar com minha edição.
Maarten Bodewes
@owlstead Sua edição está correta. Meu motivo para não incluí-lo foi o seguinte: independentemente de como 1024 * 1024 * 1024 * 1024é tratado, eu realmente queria enfatizar que não é a mesma coisa que escrever 2147473648. Há muitas maneiras (e você listou algumas) de que um idioma possa lidar com ele. É razoavelmente separado e útil. Então eu vou deixar. Muitas informações se tornam cada vez mais necessárias quando você tem uma resposta de alto nível em uma pergunta popular.
Cruncher
16

Não faço ideia por que a segunda variante não produz erros.

O comportamento que você sugere - isto é, a produção da mensagem de diagnóstico quando uma computação produz um valor maior que o maior valor que pode ser armazenado em um número inteiro - é um recurso . Para você usar qualquer recurso, o recurso deve ser pensado, considerado uma boa idéia, projetado, especificado, implementado, testado, documentado e enviado aos usuários.

Para Java, uma ou mais das coisas nessa lista não aconteceram e, portanto, você não possui o recurso. Não sei qual; você teria que perguntar a um designer Java.

Para C #, todas essas coisas aconteceram - cerca de catorze anos atrás agora - e, portanto, o programa correspondente em C # produziu um erro desde o C # 1.0.

Eric Lippert
fonte
45
Isso não adiciona nada de útil. Embora eu não me importe em fazer facadas no Java, ele não respondeu à pergunta dos OPs.
Seiyria
29
@Seiyria: O pôster original está perguntando "por que não?" pergunta - "por que o mundo não é do jeito que eu acho que deveria ser?" não é uma pergunta técnica precisa sobre o código real e, portanto, é uma pergunta ruim para o StackOverflow. O fato de a resposta correta a uma pergunta vaga e não técnica ser vaga e não técnica não deve surpreender. Encorajo o pôster original a fazer uma pergunta melhor e evite "por que não?" questões.
Eric Lippert
18
@Seiyria: A resposta aceita, notei, também não responde a essa pergunta vaga e não técnica; a pergunta é "por que isso não é um erro?" e a resposta aceita é "porque é legal". Isso está simplesmente reafirmando a questão ; respondendo "por que o céu não é verde?" com "porque é azul" não responde à pergunta. Mas como a pergunta é ruim, não culpo o respondente; a resposta é perfeitamente razoável para uma pergunta ruim.
Eric Lippert
13
Sr. Eric, esta é a pergunta que eu postei: "Por que int i = 1024 * 1024 * 1024 * 1024; sem relatório de erro no eclipse?". e a resposta de arshajii é exatamente o que eu sei (talvez mais). Às vezes, não consigo expressar nenhuma pergunta de maneira muito precisa. Acho que é por isso que algumas pessoas modificam algumas perguntas postadas com mais precisão no Stackoverflow. Acho que se eu quiser obter a resposta "porque é legal", não postarei esta pergunta. Vou tentar o meu melhor para postar algumas "perguntas regulares", mas por favor, entenda alguém como eu, que é estudante e não é tão profissional. Obrigado.
WUJ
5
@WUJ Esta resposta IMHO fornece informações e perspectivas adicionais. Depois de ler todas as respostas, encontrei essa resposta para fornecer a mesma validade que as outras respostas fornecidas. Também aumenta a conscientização de que os desenvolvedores não são os únicos implementadores de alguns produtos de software.
SoftwareCarpenter
12

Além da resposta de arshajii, quero mostrar mais uma coisa:

Não é a atribuição que causa o erro, mas simplesmente o uso do literal . Quando você tenta

long i = 2147483648;

você notará que também causa um erro de compilação, pois o lado direito ainda é um intliteral e está fora do alcance.

Portanto, operações com int-values ​​(e isso inclui atribuições) podem transbordar sem um erro de compilação (e também sem um erro de tempo de execução), mas o compilador simplesmente não pode lidar com esses literais muito grandes.

piet.t
fonte
1
Certo. A atribuição de um int a um longo inclui uma conversão implícita. Mas o valor não pode nunca existir como o int em primeiro lugar para se fundido :)
Cruncher
4

A: Porque não é um erro.

Antecedentes: A multiplicação 1024 * 1024 * 1024 * 1024levará a um estouro. Um estouro é muitas vezes um erro. Diferentes linguagens de programação produzem comportamentos diferentes quando ocorrem estouros. Por exemplo, C e C ++ chamam isso de "comportamento indefinido" para números inteiros assinados, e o comportamento é definido como números inteiros não assinados (pegue o resultado matemático, adicione UINT_MAX + 1enquanto o resultado for negativo, subtraia UINT_MAX + 1enquanto o resultado for maior que UINT_MAX).

No caso de Java, se o resultado de uma operação com intvalores não estiver no intervalo permitido, conceitualmente, o Java adiciona ou subtrai 2 ^ 32 até que o resultado esteja no intervalo permitido. Portanto, a declaração é completamente legal e não está errada. Simplesmente não produz o resultado que você esperava.

Você certamente pode discutir se esse comportamento é útil e se o compilador deve lhe dar um aviso. Eu diria pessoalmente que um aviso seria muito útil, mas um erro estaria incorreto, pois é Java legal.

gnasher729
fonte