El bucle aparentemente sin fin termina, a menos que se utilice System.out.println

91

Tenía un código simple que se suponía que era un bucle sin fin, ya xque siempre estará creciendo y siempre será más grande que j.

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

pero como está, se imprime yy no se repite sin fin. No puedo entender por qué. Sin embargo, cuando ajusto el código de la siguiente manera:

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

Se convierte en un bucle sin fin y no tengo ni idea de por qué. ¿Java reconoce que es un bucle sin fin y lo omite en la primera situación, pero tiene que ejecutar una llamada a un método en la segunda para que se comporte como se esperaba? Confundido :)

Omar
fuente
4
El segundo ciclo es infinito porque el límite superior xcrece más rápido que la variable del ciclo j. En otras palabras, jnunca alcanzará un límite superior, por lo tanto, el ciclo se ejecutará "para siempre". Bueno, no para siempre, lo más probable es que se desborde en algún momento.
Tim Biegeleisen
75
No es un bucle sin fin, es solo que se necesitan 238609294 veces para salir del bucle for en el primer caso y la segunda vez imprime el valor de y238609294 veces
N00b Pr0grammer
13
respuesta de una palabra: desbordamiento
qwr
20
Curiosamente, en System.out.println(x)lugar de yal final, habría mostrado instantáneamente cuál era el problema
JollyJoker
9
@TeroLahtinen no, no lo haría. Lea la especificación del lenguaje Java si tiene dudas sobre qué es el tipo int. Es independiente del hardware.
9ilsdx 9rvj 0lo

Respuestas:

161

Ambos ejemplos no son infinitos.

El problema es la limitación del inttipo en Java (o prácticamente cualquier otro lenguaje común). Cuando el valor de xalcanza 0x7fffffff, agregar cualquier valor positivo resultará en un desbordamiento y se volverá xnegativo, por lo tanto menor que j.

La diferencia entre el primer ciclo y el segundo es que el código interno lleva mucho más tiempo y probablemente se necesitarían varios minutos hasta que se xdesborde. Para el primer ejemplo, puede llevar menos de un segundo o lo más probable es que el optimizador elimine el código, ya que no tiene ningún efecto.

Como se mencionó en la discusión, el tiempo dependerá en gran medida de cómo el sistema operativo almacena en búfer la salida, si envía al emulador de terminal, etc., por lo que puede ser mucho más largo que unos pocos minutos.

Zbynek Vyskovsky - kvr000
fuente
48
Acabo de probar un programa (en mi computadora portátil) que imprime una línea en un bucle. Lo cronometré y pudo imprimir aproximadamente 1000 líneas / segundo. Según el comentario de N00b de que el bucle se ejecutará 238609294 veces, el bucle tardará unos 23861 segundos en finalizar, más de 6,6 horas. Un poco más de "varios minutos".
ajb
11
@ajb: Depende de la implementación. IIRC println()en Windows es una operación de bloqueo, mientras que en (¿algunos?) Unix está almacenado en búfer, por lo que va mucho más rápido. También intente usar print(), que búferes hasta que llegue a \n(o el búfer se llene, o flush()se llame)
BlueRaja - Danny Pflughoeft
6
También depende del terminal que muestre la salida. Consulte stackoverflow.com/a/21947627/53897 para ver un ejemplo extremo (donde la desaceleración se debió al ajuste de palabras)
Thorbjørn Ravn Andersen
1
Sí, está almacenado en búfer en UNIX, pero sigue bloqueando. Una vez que el búfer de 8K o más se llene, se bloqueará hasta que haya espacio. La velocidad dependerá en gran medida de la rapidez con la que se consuma. Redirigir la salida a / dev / null será lo más rápido, pero enviarlo al terminal, el valor predeterminado, requerirá actualizaciones de gráficos en la pantalla y mucha más potencia de cálculo, ya que hace que las fuentes se ralenticen.
penguin359
2
@Zbynek oh, probablemente sí, pero eso me recuerda que las E / S de los terminales generalmente se almacenarán en búfer de línea, no en bloque, por lo que lo más probable es que cada println resulte en una llamada al sistema que ralentice aún más la carcasa del terminal.
penguin359
33

Dado que se declaran como int, una vez que alcanza el valor máximo, el bucle se romperá cuando el valor de x se vuelva negativo.

Pero cuando se agrega System.out.println al bucle, la velocidad de ejecución se vuelve visible (ya que la salida a la consola ralentizará la velocidad de ejecución). Sin embargo, si deja que el segundo programa (el que tiene syso dentro del ciclo) se ejecute durante el tiempo suficiente, debería tener el mismo comportamiento que el primero (el que no tiene syso dentro del ciclo).

Ace Zachary
fuente
21
La gente no se da cuenta de cuánto spam a la consola puede ralentizar su código.
user9993
13

Puede haber dos razones para esto:

  1. Java optimiza el forciclo y, dado que no se usa xdespués del ciclo, simplemente elimina el ciclo. Puede verificar esto poniendo una System.out.println(x);declaración después del ciclo.

  2. Es posible que Java en realidad no esté optimizando el bucle y esté ejecutando el programa correctamente y, finalmente x, crezca demasiado inty se desborde. El desbordamiento de enteros probablemente hará que el entero xsea ​​negativo, que será más pequeño que j, por lo que saldrá del ciclo e imprimirá el valor de y. Esto también se puede verificar agregando System.out.println(x);después del ciclo.

Además, incluso en el primer caso, eventualmente se producirá un desbordamiento, lo que lo convertirá en el segundo caso, por lo que nunca será un verdadero bucle sin fin.

Ashok Vishnoi
fuente
14
Elijo la puerta número 2.
Robby Cornelissen
Cierto. Pasó a la escala negativa y salió del bucle. Pero a sysoutes tan lento para agregar la ilusión de un bucle infinito.
Pavan Kumar
4
1. Sería un error. Las optimizaciones del compilador no pueden cambiar el comportamiento de un programa. Si se trataba de un bucle infinito, entonces el compilador puede optimizar todo lo que quiera, sin embargo, el resultado debe ser un bucle infinito. La solución real es que el OP está equivocado: ninguno de los dos es un bucle infinito, uno solo hace más trabajo que el otro, por lo que lleva más tiempo.
Jörg W Mittag
1
@ JörgWMittag En este caso, x es una variable local sin relación con nada más. Como tal, es posible que esté optimizado. Pero uno debe mirar el código de bytes para determinar si ese es el caso, nunca asuma que el compilador hizo algo así.
HopefullyHelpful
1

Ambos no son bucles infinitos, inicialmente j = 0, siempre que j <x, j aumente (j ++), y j es un número entero, por lo que el bucle se ejecutará hasta que alcance el valor máximo y luego se desborde (un desbordamiento de enteros es la condición que ocurre cuando el resultado de una operación aritmética, como una multiplicación o suma, excede el tamaño máximo del tipo de entero utilizado para almacenarlo). para el segundo ejemplo, el sistema simplemente imprime el valor de y hasta que se rompe el ciclo.

si está buscando un ejemplo de un bucle sin fin, debería verse así

int x = 6;

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

porque (x) nunca alcanzaría el valor de 10;

también puede crear un bucle infinito con un bucle for doble:

int i ;

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

este bucle es infinito porque el primer bucle for dice i <10, lo cual es cierto, así que entra en el segundo bucle for y el segundo bucle for aumenta el valor de (i) hasta que es == 5. Luego pasa al primero for bucle de nuevo porque i <10, el proceso sigue repitiéndose porque se reinicia después del segundo bucle for

Kennedy
fuente
1

Es un ciclo finito porque una vez que el valor de xexcede 2,147,483,647(que es el valor máximo de an int), xse volverá negativo y no mayor que jcualquier otro, ya sea que imprima y o no.

Puede simplemente cambiar el valor de ya 100000e imprimir yen el ciclo y el ciclo se romperá muy pronto.

La razón por la que sientes que se volvió infinito es que System.out.println(y);hizo que el código se ejecutara mucho más lento que sin ninguna acción.

Joe Cheng
fuente
0

Problema interesante En realidad, en ambos casos, el bucle no es interminable

Pero la principal diferencia entre ellos es cuándo terminará y cuánto tiempo xllevará exceder el intvalor máximo , que es 2,147,483,647después de que alcanzará el estado de desbordamiento y el ciclo terminará.

La mejor manera de comprender este problema es probar un ejemplo simple y conservar sus resultados.

Ejemplo :

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

Salida:

finished!
BUILD SUCCESSFUL (total time: 0 seconds)

Después de probar este bucle infinito, tardará menos de 1 segundo en terminar.

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

Salida:

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

En este caso de prueba, notará una gran diferencia en el tiempo necesario para finalizar y finalizar la ejecución del programa.

Si no tiene paciencia, pensará que este ciclo es interminable y no terminará, pero de hecho tomará horas terminar y alcanzar el estado de desbordamiento en el ivalor.

Finalmente, concluimos, después de poner la declaración de impresión dentro del bucle for, que tomará mucho más tiempo que el bucle en el primer caso sin la declaración de impresión.

El tiempo necesario para ejecutar el programa depende de las especificaciones de su computadora, en particular la potencia de procesamiento (capacidad del procesador), el sistema operativo y su IDE que está compilando el programa.

Pruebo este caso en:

Lenovo Intel Core i5 de 2,7 GHz

SO: Windows 8.1 64x

IDE: NetBeans 8.2

Se tarda unas 8 horas (486 minutos) en finalizar el programa.

También puede notar que el incremento de paso en el ciclo for i = i + 1es un factor muy lento para alcanzar el valor máximo de int.

Podemos cambiar este factor y hacer que el incremento de pasos sea más rápido para probar el bucle en menos tiempo.

si lo ponemos i = i * 10y lo probamos:

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

Salida:

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

Como ves, es muy rápido en comparación con el ciclo anterior.

toma menos de 1 segundo terminar y terminar de ejecutar el programa.

Después de este ejemplo de prueba, creo que debería aclarar el problema y probar la validez de Zbynek Vyskovsky: la respuesta de kvr000 , también será la respuesta a esta pregunta .

Oghli
fuente