Loop aparentemente infinito termina, a menos que System.out.println seja usado

91

Eu tinha um código simples que deveria ser um loop infinito, pois xsempre estará crescendo e sempre permanecerá maior que j.

int x = 5;
int y = 9;
for (int j = 0; j < x; j++) {
   x = x + y;
}
System.out.println(y);

mas como está, ele imprime ye não faz loop indefinidamente. Eu não consigo descobrir o porquê. No entanto, quando ajusto o código da seguinte maneira:

int x = 5;
int y = 9;
for (int j = 0; j < x; j++) {
    x = x + y;
    System.out.println(y);
}
System.out.println(y);

Torna-se um ciclo sem fim e não tenho ideia do porquê. O java reconhece que é um loop infinito e o ignora na primeira situação, mas precisa executar uma chamada de método na segunda para que se comporte conforme o esperado? Confuso :)

Omar
fonte
4
O segundo loop é infinito porque o limite superior xcresce mais rápido do que a variável do loop j. Em outras palavras, jnunca atingirá um limite superior, portanto, o loop será executado "para sempre". Bem, não para sempre, você provavelmente terá um estouro em algum ponto.
Tim Biegeleisen 01 de
75
Não é um loop infinito, apenas leva 238609294 vezes para sair do loop for no primeiro caso e, na segunda vez, imprime o valor de y238609294 vezes
N00b Pr0grammer
13
resposta de uma palavra: estouro
qwr
20
Curiosamente, em System.out.println(x)vez de yno final, teria mostrado instantaneamente qual era o problema
JollyJoker
9
@TeroLahtinen não, não seria. Leia as especificações da linguagem Java se tiver dúvidas sobre o tipo int. É independente de hardware.
9ilsdx 9rvj 0lo 01 de

Respostas:

161

Ambos os exemplos não são infinitos.

O problema é a limitação do inttipo em Java (ou praticamente qualquer outra linguagem comum). Quando o valor de xatingir 0x7fffffff, adicionar qualquer valor positivo resultará em estouro e o xtorna - se negativo, portanto menor que j.

A diferença entre o primeiro e o segundo loop é que o código interno leva muito mais tempo e provavelmente levaria vários minutos até xestourar. Para o primeiro exemplo, pode levar menos de um segundo ou muito provavelmente o código será removido pelo otimizador, pois não tem nenhum efeito.

Conforme mencionado na discussão, o tempo dependerá muito de como o SO armazena a saída, se ela é enviada para o emulador de terminal etc., portanto, pode ser muito maior do que alguns minutos.

Zbynek Vyskovsky - kvr000
fonte
48
Acabei de experimentar um programa (no meu laptop) que imprime uma linha em um loop. Eu cronometrei e ele foi capaz de imprimir aproximadamente 1000 linhas / segundo. Com base no comentário do N00b de que o loop será executado 238609294 vezes, levará cerca de 23861 segundos para o loop terminar - mais de 6,6 horas. Um pouco mais do que "vários minutos".
ajb 01 de
11
@ajb: Depende da implementação. O IIRC println()no Windows é uma operação de bloqueio, enquanto em (alguns?) Unix ele é armazenado em buffer e é muito mais rápido. Também tente usar print(), que buffers até atingir um \n(ou o buffer se encher ou flush()ser chamado)
BlueRaja - Danny Pflughoeft
6
Também depende do terminal mostrando a saída. Consulte stackoverflow.com/a/21947627/53897 para um exemplo extremo (onde a desaceleração foi devido à quebra de linha)
Thorbjørn Ravn Andersen
1
Sim, é armazenado em buffer no UNIX, mas ainda está bloqueando. Assim que o buffer de 8K ou mais for preenchido, ele bloqueará até que haja espaço. A velocidade dependerá muito da rapidez com que é consumida. Redirecionar a saída para / dev / null será o mais rápido, mas enviá-la para o terminal, o padrão, exigirá atualizações de gráficos na tela e muito mais poder de computação, pois renderiza as fontes, tornando-as mais lentas.
penguin359 01 de
2
@Zbynek oh, provavelmente sim, mas isso me lembra, os terminais de E / S normalmente serão armazenados em buffer de linha, e não em bloco, então provavelmente cada println resultará em uma chamada de sistema, tornando mais lenta a caixa do terminal.
penguin359
33

Uma vez que eles são declarados como int, assim que atingir o valor máximo, o loop será interrompido e o valor x se tornará negativo.

Mas quando System.out.println é adicionado ao loop, a velocidade de execução se torna visível (já que a saída para o console diminuirá a velocidade de execução). No entanto, se você deixar o segundo programa (aquele com syso dentro do loop) funcionar por tempo suficiente, ele deverá ter o mesmo comportamento do primeiro (aquele sem syso dentro do loop).

Ace Zachary
fonte
21
As pessoas não percebem quanto spam para o console pode tornar seu código mais lento.
user9993 01 de
13

Pode haver dois motivos para isso:

  1. Java otimiza o forloop e, como não há uso de xafter the loop, simplesmente remove o loop. Você pode verificar isso colocando System.out.println(x);instrução após o loop.

  2. Pode ser possível que o Java não esteja realmente otimizando o loop e esteja executando o programa corretamente e, eventualmente, xficará grande demais para intestourar. O estouro de inteiro provavelmente tornará o inteiro xnegativo, o que será menor que j e, portanto, sairá do loop e imprimirá o valor de y. Isso também pode ser verificado adicionando System.out.println(x);após o loop.

Além disso, mesmo no primeiro caso, eventualmente, o estouro ocorrerá, tornando-o no segundo caso, de forma que nunca será um verdadeiro loop infinito.

Ashok Vishnoi
fonte
14
Eu escolho a porta número 2.
Robby Cornelissen
Verdade. Ele entrou na escala negativa e saiu do loop. Mas a sysouté tão lento para adicionar a ilusão de um loop infinito.
Pavan Kumar
4
1. Seria um bug. As otimizações do compilador não podem alterar o comportamento de um programa. Se for um loop infinito, o compilador pode otimizar tudo o que deseja; no entanto, o resultado ainda deve ser um loop infinito. A solução real é que o OP está errado: nenhum dos dois é um loop infinito, um apenas faz mais trabalho do que o outro, por isso leva mais tempo.
Jörg W Mittag
1
@ JörgWMittag Neste caso, x é uma variável local sem relação com qualquer outra coisa. Como tal, é possível que seja totalmente otimizado. Mas deve-se olhar o bytecode para determinar se é esse o caso, nunca apenas assumir que o compilador fez algo assim.
Esperançosamente útil
1

Ambos não são loops infinitos, inicialmente j = 0, enquanto j <x, j aumenta (j ++), ej é um número inteiro, então o loop seria executado até atingir o valor máximo e então estouraria (Um estouro de inteiro é a condição que ocorre quando o resultado de uma operação aritmética, como multiplicação ou adição, excede o tamanho máximo do tipo inteiro usado para armazená-lo.). para o segundo exemplo, o sistema apenas imprime o valor de y até que o loop seja interrompido.

se você estiver procurando por um exemplo de loop infinito, deve ser parecido com este

int x = 6;

for (int i = 0; x < 10; i++) {
System.out.println("Still Looping");
}

porque (x) nunca alcançaria o valor de 10;

você também pode criar um loop infinito com um loop for duplo:

int i ;

  for (i = 0; i <= 10; i++) {
      for (i = 0; i <= 5; i++){
         System.out.println("Repeat");   
      }
 }

este laço é infinito porque o primeiro laço for diz i <10, o que é verdade, então ele vai para o segundo laço for e o segundo laço for aumenta o valor de (i) até que seja == 5. Então ele prossegue para o primeiro loop for novamente porque i <10, o processo continua se repetindo porque ele reinicia após o segundo loop for

Kennedy
fonte
1

É um loop finito porque uma vez que o valor de xexcede 2,147,483,647(que é o valor máximo de an int), xse tornará negativo e não maior do que jqualquer mais, quer você imprima y ou não.

Você pode apenas alterar o valor de ypara 100000e imprimir yno loop e o loop será interrompido muito em breve.

A razão pela qual você acha que ele se tornou infinito é que ele System.out.println(y);fez o código ser executado muito mais lento do que sem nenhuma ação.

Joe Cheng
fonte
0

Problema interessante Na verdade, em ambos os casos, o loop não é infinito

Mas a principal diferença entre eles é quando terminará e quanto tempo xlevará para exceder o intvalor máximo que é2,147,483,647 depois disso atingirá o estado de estouro e o loop terminará.

A melhor maneira de entender esse problema é testar um exemplo simples e preservar seus resultados.

Exemplo :

for(int i = 10; i > 0; i++) {}
System.out.println("finished!");

Resultado:

finished!
BUILD SUCCESSFUL (total time: 0 seconds)

Depois de testar este loop infinito, levará menos de 1 segundo para terminar.

for(int i = 10; i > 0; i++) {
    System.out.println("infinite: " + i);
}
System.out.println("finished!");

Resultado:

infinite: 314572809
infinite: 314572810
infinite: 314572811
.
.
.
infinite: 2147483644
infinite: 2147483645
infinite: 2147483646
infinite: 2147483647
finished!
BUILD SUCCESSFUL (total time: 486 minutes 25 seconds)

Neste caso de teste, você notará uma grande diferença no tempo necessário para encerrar e concluir a execução do programa.

Se você não tiver paciência, pensará que esse loop é interminável e não terminará, mas na verdade levará horas para terminar e atingir o estado de estouro no ivalor.

Finalmente concluímos, depois de colocar a instrução print dentro do loop for, que levará muito mais tempo do que o loop no primeiro caso sem a instrução print.

O tempo necessário para executar o programa depende das especificações do seu computador, em particular da capacidade de processamento (capacidade do processador), do sistema operacional e do IDE que está compilando o programa.

Eu testo este caso em:

Lenovo 2.7 GHz Intel Core i5

SO: Windows 8.1 64x

IDE: NetBeans 8.2

Demora cerca de 8 horas (486 minutos) para terminar o programa.

Além disso, você pode notar que o incremento de passo no loop for i = i + 1 for é um fator muito lento para atingir o valor máximo int.

Podemos alterar esse fator e tornar o incremento de etapa mais rápido para testar o loop em menos tempo.

se colocarmos i = i * 10e testarmos:

for(int i = 10; i > 0; i*=10) {
           System.out.println("infinite: " + i);
}
     System.out.println("finished!");

Resultado:

infinite: 100000
infinite: 1000000
infinite: 10000000
infinite: 100000000
infinite: 1000000000
infinite: 1410065408
infinite: 1215752192
finished!
BUILD SUCCESSFUL (total time: 0 seconds)

Como você pode ver, é muito rápido em comparação com o loop anterior

leva menos de 1 segundo para encerrar e concluir a execução do programa.

Após este exemplo de teste, acho que deve esclarecer o problema e provar a validade de Zbynek Vyskovsky - a resposta do kvr000 , também será a resposta a esta pergunta .

Oghli
fonte