¿Se detiene este bucle 'for' y por qué / por qué no? para (var i = 0; 1 / i> 0; i ++) {}

104

¿Este forbucle se detiene alguna vez?

for (var i=0; 1/i > 0; i++) {
}

Si es así, cuándo y por qué? Me dijeron que se detiene, pero no me dieron ninguna razón para ello.

Actualización

Como parte de la investigación, he escrito un artículo bastante extenso y detallado que explica todo lo que está sucediendo bajo el capó: esto es lo que necesita saber sobre el tipo de número de JavaScript

Max Koretskyi
fuente
5
No se detendrá. intente ejecutar este trozo de código. para (var i = 0; 1 / i> 0; i ++) {console.log (i)}
Sourabh Agrawal
3
Number.MAX_VALUE + 9.979202e291 == "Infinity" y 1 / (NaN o 'Infinity' o 'undefined')> 0 == falso.
Askeet
6
¿Javascript ignorará ese bucle porque no tiene declaraciones dentro? es decir, ¿optimizarlo? Sé que hay algunos lenguajes compilados que lo harían.
Brian J
3
@askeet, como gotnull y otros señalan a continuación, nunca llegamos a Infinity aumentando repetidamente, sino que quedamos atrapados en un bucle después Number.MAX_SAFE_INTEGER + 1.
LSpice

Respuestas:

128

(No soy un fanático del metacontenido , pero: las respuestas de gotnull y le_m son correctas y útiles. Originalmente lo eran, y lo son aún más con las ediciones realizadas después de la publicación de esta Wiki de la comunidad. La motivación original de este CW desapareció en gran medida como resultado de esas ediciones, pero sigue siendo útil, así que ... Además: si bien solo hay un par de autores en la lista, muchos otros miembros de la comunidad han ayudado mucho con comentarios que se han incorporado y limpiado. no es solo un CW de nombre).


El ciclo no se detendrá en un motor JavaScript implementado correctamente. (El entorno de host del motor podría eventualmente terminarlo porque es interminable, pero eso es otra cosa).

Este es el por qué:

  1. Inicialmente, cuando ies 0, la condición 1/i > 0es verdadera porque en JavaScript, 1/0es Infinityy Infinity > 0es verdadera.

  2. Después de eso, ise incrementará y seguirá creciendo como un valor entero positivo durante mucho tiempo (otras 9,007,199,254,740,991 iteraciones). En todos esos casos, 1/ipermanecerá > 0(¡aunque los valores de se 1/ivuelven realmente pequeños hacia el final!) Y así el ciclo continúa hasta e incluyendo el ciclo donde ialcanza el valor Number.MAX_SAFE_INTEGER.

  3. Los números en JavaScript son de coma flotante binaria de doble precisión IEEE-754, un formato bastante compacto (64 bits) que proporciona cálculos rápidos y una amplia gama. Lo hace almacenando el número como un bit de signo, un exponente de 11 bits y un significando de 52 bits (aunque gracias a la astucia, en realidad obtiene 53 bits de precisión). Es un punto flotante binario (base 2): el significado (más un poco de inteligencia) nos da el valor, y el exponente nos da la magnitud del número.

    Naturalmente, con tantos bits significativos, no se pueden almacenar todos los números. Aquí es el número 1, y el siguiente número más alto después del 1 de que el formato puede almacenar, 1 + 2 -52 ≈ 1.00000000000000022, y el siguiente más alto después de que 1 + 2 × 2 -52 ≈ 1.00000000000000044:

       + ------------------------------------------------- -------------- firmar bit
      / + ------- + ---------------------------------------- -------------- exponente
     / / | + ------------------------------------------------- + - significando
    / / | / |
    0 01111111111 0000000000000000000000000000000000000000000000000000
                    = 1
    0 01111111111 0000000000000000000000000000000000000000000000000001
                    ≈ 1.00000000000000022
    0 01111111111 0000000000000000000000000000000000000000000000000010
                    ≈ 1.00000000000000044
    

    Note el salto de 1.00000000000000022 a 1.00000000000000044; no hay forma de almacenar 1.0000000000000003. Eso también puede suceder con los números enteros: Number.MAX_SAFE_INTEGER(9,007,199,254,740,991) es el valor entero positivo más alto que el formato puede contener donde iy i + 1ambos son exactamente representables ( especificación ). Se pueden representar tanto 9,007,199,254,740,991 como 9,007,199,254,740,992, pero el siguiente entero, 9,007,199,254,740,993, no puede; el siguiente entero que podemos representar después de 9,007,199,254,740,992 es 9,007,199,254,740,994. Aquí están los patrones de bits, tenga en cuenta el bit más a la derecha (menos significativo):

       + ------------------------------------------------- -------------- firmar bit
      / + ------- + ---------------------------------------- -------------- exponente
     / / | + ------------------------------------------------- + - significando
    / / | / |
    0 10000110011 11111111111111111111111111111111111111111111111111
                    = 9007199254740991 (Número.MAX_SAFE_INTEGER)
    0 10000110100 0000000000000000000000000000000000000000000000000000
                    = 9007199254740992 (Número.MAX_SAFE_INTEGER + 1)
    x xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                      9007199254740993 (Número.MAX_SAFE_INTEGER + 2) no se puede almacenar
    0 10000110100 0000000000000000000000000000000000000000000000000001
                    = 9007199254740994 (Número.MAX_SAFE_INTEGER + 3)
    

    Recuerde, el formato es base 2, y con ese exponente el bit menos significativo ya no es fraccionario; tiene un valor de 2. Puede estar desactivado (9,007,199,254,740,992) o activado (9,007,199,254,740,994); así que en este punto, hemos comenzado a perder precisión incluso en la escala de números enteros (enteros). ¡Lo que tiene implicaciones para nuestro bucle!

  4. Después de completar el i = 9,007,199,254,740,992ciclo, i++nos da ... de i = 9,007,199,254,740,992nuevo; no hay ningún cambio i, porque el siguiente entero no se puede almacenar y el cálculo termina redondeando hacia abajo. icambiaría si lo hiciéramos i += 2, pero i++no podemos cambiarlo. Así que hemos alcanzado el estado estable: inunca cambia y el ciclo nunca termina.

Aquí están los diversos cálculos relevantes:

if (!Number.MAX_SAFE_INTEGER) {
  // Browser doesn't have the Number.MAX_SAFE_INTEGER
  // property; shim it. Should use Object.defineProperty
  // but hey, maybe it's so old it doesn't have that either
  Number.MAX_SAFE_INTEGER = 9007199254740991;
}
var i = 0;
console.log(i, 1/i, 1/i > 0); // 0, Infinity, true
i++;
console.log(i, 1/i, 1/i > 0); // 1, 1, true
// ...eventually i is incremented all the way to Number.MAX_SAFE_INTEGER
i = Number.MAX_SAFE_INTEGER;
console.log(i, 1/i, 1/i > 0); // 9007199254740991 1.1102230246251568e-16, true
i++;
console.log(i, 1/i, 1/i > 0); // 9007199254740992 1.1102230246251565e-16, true
i++;
console.log(i, 1/i, 1/i > 0); // 9007199254740992 1.1102230246251565e-16, true (no change)
console.log(i == i + 1);      // true

TJ Crowder
fuente
79

Responder:

La condición 1/i > 0siempre se evaluará como verdadera:

  • Inicialmente es cierto porque 1/0se evalúa como Infinityy Infinity > 0es cierto

  • Sigue siendo cierto ya que 1/i > 0es cierto para todos i < Infinityy i++nunca llega Infinity.

¿Por qué i++nunca llega Infinity? Debido a la precisión limitada del Numbertipo de datos, existe un valor para el cual i + 1 == i:

9007199254740992 + 1 == 9007199254740992 // true

Una vez ique alcance ese valor (que corresponde a ), seguirá siendo el mismo incluso después .Number.MAX_SAFE_INTEGER + 1i++

Por tanto, tenemos un bucle infinito.


Apéndice:

¿Por qué es 9007199254740992 + 1 == 9007199254740992?

El Numbertipo de datos de JavaScript es en realidad un flotante de doble precisión IEEE 754 de 64 bits . Cada uno Numberse desmonta y almacena en tres partes: signo de 1 bit, exponente de 11 bits y mantisa de 52 bits. Su valor es -1 signo × mantisa × 2 exponente .

¿Cómo se representa 9007199254740992 ? Como 1.0 × 2 53 , o en binario:

ingrese la descripción de la imagen aquí

Incrementando el bit menos significativo de la mantisa, obtenemos el siguiente número más alto:

ingrese la descripción de la imagen aquí

El valor de ese número es 1.00000000000000022… × 2 53 = 9007199254740994

Qué significa eso? Numberpuede ser 900719925474099 2 o 900719925474099 4 , pero nada intermedio.

Ahora, ¿cuál elegiremos para representar 900719925474099 2 + 1 ? Las reglas de redondeo de IEEE 754 dan la respuesta: 900719925474099 2 .

le_m
fuente
9
breve y correcto, mejor que la respuesta aceptada actual
AlexWien
@AlexWien La respuesta aceptada es una respuesta aceptada por la wiki de la comunidad.
Fulvio
2
No conozco la respuesta del término "comunidad wiki aceptada". ¿Qué tiene esto que ver con stackoverflow? Si se trata de un enlace externo, se debe proporcionar un enlace. Las respuestas aceptadas en stackoverflow siempre pueden cambiar, el estado aceptado no es definitivo.
AlexWien
"¿Por qué i ++ nunca alcanza el infinito? Debido a la precisión limitada del tipo de datos numérico ..." <- Seguramente nunca llegaría al infinito, incluso con un tipo de número de precisión infinita ... Ya sabes, porque no puedes contar hasta infinity: P
Blorgbeard sale el
1
@Blorgbeard Usted puede contar hasta infinito con la precisión de dobles limitadas, sólo hay que incrementan en un número mucho más grande que 1, por ejemplo for (var i = 0; i < Infinity; i += 1E306);. Pero entiendo de dónde vienes;)
le_m
27

La Number.MAX_SAFE_INTEGERconstante representa el número entero seguro máximo en JavaScript. La MAX_SAFE_INTEGERconstante tiene un valor de 9007199254740991. El razonamiento detrás de ese número es que JavaScript usa números de formato de punto flotante de doble precisión como se especifica en IEEE 754 y solo puede representar con seguridad números entre - (2 53 - 1) y 2 53 - 1.

Seguro en este contexto se refiere a la capacidad de representar números enteros de forma exacta y compararlos correctamente. Por ejemplo, Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2evaluará a true, que es matemáticamente incorrecto. Consulte Number.isSafeInteger()para obtener más información.

Debido a que MAX_SAFE_INTEGERes una propiedad estática de Number, siempre la usa Number.MAX_SAFE_INTEGERcomo una propiedad de un Numberobjeto que ha creado y no como una propiedad .

ACTUALIZAR:

Alguien en una respuesta que se eliminó mencionó: inunca llegará al infinito. Una vez que llega Number.MAX_SAFE_INTEGER, i++ya no incrementa la variable. De hecho, esto no es correcto.

@TJ Crowder comenta que i = Number.MAX_SAFE_INTEGER; i++; i == Number.MAX_SAFE_INTEGER;es false. Pero la siguiente iteración alcanza un estado invariable, por lo que la respuesta en main es correcta.

ien el ejemplo nunca llega Infinity.

fulvio
fuente
2
Específicamente, 9007199254740992 + 1es 9007199254740992.
Kobi
1
@GerardoFurtado Me lo imagino.
fulvio
1
@GerardoFurtado for (var i=0; NaN > 0; i++) { console.log(i); }no producirá nada.
fulvio
2
@GerardoFurtado: En ese caso, el bucle se detendría. El cuerpo del bucle nunca se ingresaría, ya que la primera prueba ( 1/i > 0) sería falsa, ya que si ies 0, 1/ies NaNy NaN > 0es falso.
TJ Crowder
1
@TJCrowder He actualizado mi respuesta. ¡Gracias por señalar eso!
fulvio