El bucle aparentemente interminable termina, a menos que se use System.out.println

10 minutos de lectura

avatar de usuario
Omar

Tenía un poco de código simple que era supuesto ser un bucle sin fin desde x siempre estará creciendo y siempre permanecerá 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 es, se imprime y y no se repite sin cesar. 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 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 de método en la segunda para que se comporte como se esperaba? Confundido 🙂

  • El segundo ciclo es interminable porque el límite superior x crece más rápido que la variable de bucle j. En otras palabras, j nunca alcanzará un límite superior, por lo que el bucle se ejecutará “para siempre”. Bueno, no para siempre, lo más probable es que tengas un desbordamiento en algún momento.

    – Tim Biegeleisen

    1 de febrero de 2017 a las 4:53

  • No es un bucle sin fin, es solo que tarda 238609294 veces en salir del bucle for en el primer caso y la segunda vez imprime el valor de y 238609294 veces

    – N00b Programador

    1 de febrero de 2017 a las 4:58

  • respuesta de una palabra: Desbordamiento

    – qwr

    1 de febrero de 2017 a las 6:20

  • Graciosamente, System.out.println(x) en vez de y al final habría mostrado instantáneamente cuál era el problema

    – Bromista alegre

    1 de febrero de 2017 a las 10:54

  • @TeroLahtinen no, no lo haría. Lea las especificaciones del lenguaje Java si tiene dudas sobre qué es el tipo int. Es independiente del hardware.

    – 9ilsdx 9rvj 0lo

    1 de febrero de 2017 a las 11:37

avatar de usuario
Zbynek Vyskovsky – kvr000

Ambos ejemplos no son infinitos.

El problema es la limitación de int escriba en Java (o prácticamente cualquier otro lenguaje común). Cuando el valor de x alcanza 0x7fffffffagregar cualquier valor positivo resultará en un desbordamiento y el x se vuelve negativo, por lo tanto menor que j.

La diferencia entre el primer bucle y el segundo es que el código interno lleva mucho más tiempo y probablemente tardaría varios minutos hasta que x se desborda Para el primer ejemplo, puede tomar menos del 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 almacene en búfer la salida, si se envía al emulador de terminal, etc., por lo que puede ser mucho mayor que unos pocos minutos.

  • Acabo de probar un programa (en mi computadora portátil) que imprime una línea en un bucle. Lo cronometré y fue capaz de 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 terminar, más de 6,6 horas. Un poco más que “varios minutos”.

    – ajb

    1 de febrero de 2017 a las 5:29


  • @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 amortigua hasta que llega a un \n (o el búfer se llena, o flush() se llama)

    – Blue Raja – Danny Pflughoeft

    1 de febrero de 2017 a las 8:40


  • También depende de la terminal que muestre la salida. Consulte stackoverflow.com/a/21947627/53897 para ver un ejemplo extremo (donde la ralentización se debió al ajuste de palabras)

    – Thorbjorn Ravn Andersen

    1 de febrero de 2017 a las 17:40

  • Sí, está almacenado en UNIX, pero sigue bloqueando. Una vez que se llene el búfer de aproximadamente 8K, 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 a la terminal, el valor predeterminado, requerirá actualizaciones de gráficos en la pantalla y mucho más poder de cómputo, ya que las fuentes se ralentizan.

    – pingüino359

    1 de febrero de 2017 a las 19:59


  • @Zbynek oh, probablemente sí, pero eso me recuerda que las E/S de los terminales normalmente se almacenarán en línea, no se bloquearán, por lo que lo más probable es que cada impresión resulte en una llamada al sistema que ralentiza aún más la caja del terminal.

    – pingüino359

    2 de febrero de 2017 a las 4:44

Dado que se declaran como int, una vez que alcanza el valor máximo, el ciclo se romperá ya que el valor de x se volverá negativo.

Pero cuando se agrega System.out.println al ciclo, 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 bucle) se ejecute durante el tiempo suficiente, debería tener el mismo comportamiento que el primero (el que no tiene syso dentro del bucle).

  • La gente no se da cuenta de cuánto spam en la consola puede ralentizar su código.

    – usuario9993

    1 de febrero de 2017 a las 14:31

avatar de usuario
Ashok Vishnoi

Puede haber dos razones para esto:

  1. Java optimiza la for bucle y dado que no hay uso de x después del bucle, simplemente elimina el bucle. Puedes verificar esto poniendo System.out.println(x); instrucción después del bucle.

  2. Es posible que Java no esté realmente optimizando el ciclo y que esté ejecutando el programa correctamente y eventualmente x crecerá demasiado para int y desbordamiento. El desbordamiento de enteros probablemente hará que el entero x como 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 bucle.

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

  • Elijo la puerta número 2.

    –Robby Cornelissen

    1 de febrero de 2017 a las 5:00

  • Verdadero. Pasó a la escala negativa y salió del ciclo. pero un sysout es tan lento para agregar una ilusión de un bucle infinito.

    – Pavan Kumar

    1 de febrero de 2017 a las 5:08

  • 1. Sería un error. Las optimizaciones del compilador no pueden cambiar el comportamiento de un programa. Si se trata de un ciclo infinito, entonces el compilador puede optimizar todo lo que quiera, sin embargo, el resultado aún debe ser un ciclo infinito. La solución real es que el OP está equivocado: ninguno de los dos es un ciclo infinito, uno solo hace más trabajo que el otro, por lo que lleva más tiempo.

    – Jörg W. Mittag

    1 de febrero de 2017 a las 15:09


  • @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í.

    – Esperemos que sea útil

    2 de febrero de 2017 a las 9:05


Ninguno de los dos son bucles infinitos, inicialmente j = 0, siempre que j

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 podrías 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

Es un ciclo finito porque una vez que el valor de x excede 2,147,483,647 (que es el valor máximo de un int), x será negativo y no mayor que j más, ya sea que imprima y o no.

Simplemente puede cambiar el valor de y a 100000 e imprimir y en el bucle y el bucle se romperá muy pronto.

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

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 x tardará en superar el máximo int valor que es 2,147,483,647 después de eso, alcanzará el estado de desbordamiento y el ciclo terminará.

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

Ejemplo:

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

Producción:

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!");

Producción:

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 terminar de ejecutar el programa.

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

Finalmente, llegamos a la conclusión de que, después de colocar la declaración de impresión dentro del ciclo for, tomará mucho más tiempo que el primer caso sin 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:

Intel Core i5 de 2,7 GHz de Lenovo

Sistema operativo: Windows 8.1 64x

IDE: NetBeans 8.2

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

También puede notar que el incremento de paso en el ciclo for i = i + 1 es 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 ponemos i = i * 10 y probarlo:

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

Producción:

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 bucle anterior.

tarda menos de 1 segundo en 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 la respuesta de Zbynek Vyskovsky – kvr000, también será la respuesta a esta pregunta.

¿Ha sido útil esta solución?