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.


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 ).


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.


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.


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.


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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.