Cette boucle «pour» s'arrête-t-elle, et pourquoi / pourquoi pas? pour (var i = 0; 1 / i> 0; i ++) {}


104

Cette forboucle s'arrête- t-elle jamais?

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

Si oui, quand et pourquoi? On m'a dit que ça s'arrête, mais on ne m'a donné aucune raison à cela.

Mettre à jour

Dans le cadre de l'enquête, j'ai écrit un article assez long et détaillé qui explique tout ce qui se passe sous le capot - Voici ce que vous devez savoir sur le type de nombre de JavaScript


5
Ça ne s'arrêtera pas. essayez d'exécuter ce morceau de code. for (var i = 0; 1 / i> 0; i ++) {console.log (i)}
Sourabh Agrawal


3
Number.MAX_VALUE + 9.979202e291 == "Infinity" et 1 / (NaN ou 'Infinity' ou 'undefined')> 0 == false.
askeet

6
Javascript ignorera-t-il cette boucle car il ne contient aucune instruction? c'est-à-dire l'optimiser loin? Je sais que certains langages compilés le feraient.
Brian J

3
@askeet, comme gotnull et d'autres le soulignent ci-dessous, nous n'atteignons jamais Infinity en incrémentant à plusieurs reprises, au lieu d'être pris dans une boucle après Number.MAX_SAFE_INTEGER + 1.
LSpice

Réponses:


128

(Je ne suis pas fan du méta-contenu, mais: les réponses de gotnull et le_m sont à la fois correctes et utiles. Elles l'étaient à l'origine, et le sont encore plus avec les modifications apportées après la publication de ce wiki communautaire. La motivation originale de cette CW a en grande partie disparu à la suite de ces modifications, mais cela reste utile, donc ... Aussi: bien qu'il n'y ait que quelques auteurs répertoriés, de nombreux autres membres de la communauté ont grandement contribué avec des commentaires qui ont été intégrés et nettoyés. n'est pas seulement un CW de nom.)


La boucle ne s'arrêtera pas dans un moteur JavaScript correctement implémenté. (L'environnement hôte du moteur peut éventuellement le terminer car il est sans fin, mais c'est autre chose.)

Voici pourquoi:

  1. Au départ, quand iest 0, la condition 1/i > 0est vraie parce que dans JavaScript, 1/0est Infinityet Infinity > 0est vrai.

  2. Après cela, isera incrémenté et continuera de croître en tant que valeur entière positive pendant une longue période (9 007 199 254 740 991 itérations supplémentaires). Dans tous ces cas, 1/irestera > 0(bien que les valeurs de 1/ideviennent vraiment petites vers la fin!) Et ainsi la boucle continue jusqu'à et y compris la boucle où iatteint la valeur Number.MAX_SAFE_INTEGER.

  3. Les nombres en JavaScript sont à virgule flottante binaire double précision IEEE-754, un format assez compact (64 bits) qui permet des calculs rapides et une vaste plage. Il le fait en stockant le nombre sous forme de bit de signe, d'exposant de 11 bits et de significande de 52 bits (bien que grâce à son intelligence, il obtienne en fait 53 bits de précision). C'est une virgule flottante binaire (base 2): le significande (plus une certaine intelligence) nous donne la valeur, et l'exposant nous donne la grandeur du nombre.

    Naturellement, avec juste autant de bits significatifs, tous les nombres ne peuvent pas être stockés. Voici le numéro 1, et le numéro suivant le plus élevé après 1 que le format peut stocker, 1 + 2 -52 ≈ 1.00000000000000022, et le prochain plus élevé après cela 1 + 2 × 2 -52 ≈ 1.00000000000000044:

       + ------------------------------------------------- -------------- signe bit
      / + ------- + ---------------------------------------- -------------- exposant
     / / | + ------------------------------------------------- + - significand
    / / | / |
    0 01111111111 0000000000000000000000000000000000000000000000000000
                    = 1
    0 01111111111 0000000000000000000000000000000000000000000000000001
                    ≈ 1.00000000000000022
    0 01111111111 0000000000000000000000000000000000000000000000000010
                    ≈ 1.00000000000000044
    

    Notez le saut de 1.00000000000000022 à 1.00000000000000044; il n'y a aucun moyen de stocker 1.0000000000000003. Cela peut également se produire avec des entiers: Number.MAX_SAFE_INTEGER(9,007,199,254,740,991) est la valeur entière positive la plus élevée que le format peut contenir où iet i + 1sont tous les deux exactement représentables ( spec ). 9 007 199 254 740 991 et 9 007 199 254 740 992 peuvent être représentés, mais l' entier suivant , 9 007 199 254 740 993, ne le peut pas; le prochain entier que nous pouvons représenter après 9,007,199,254,740,992 est 9,007,199,254,740,994. Voici les modèles de bits, notez le bit le plus à droite (le moins significatif):

       + ------------------------------------------------- -------------- signe bit
      / + ------- + ---------------------------------------- -------------- exposant
     / / | + ------------------------------------------------- + - significand
    / / | / |
    0 10000110011 1111111111111111111111111111111111111111111111111111
                    = 9007199254740991 (Numéro.MAX_SAFE_INTEGER)
    0 10000110100 0000000000000000000000000000000000000000000000000000
                    = 9007199254740992 (Numéro.MAX_SAFE_INTEGER + 1)
    x xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                      9007199254740993 (Number.MAX_SAFE_INTEGER + 2) ne peut pas être stocké
    0 10000110100 0000000000000000000000000000000000000000000000000001
                    = 9007199254740994 (Numéro.MAX_SAFE_INTEGER + 3)
    

    Souvenez-vous que le format est en base 2, et avec cet exposant, le bit le moins significatif n'est plus fractionnaire; il a une valeur de 2. Il peut être désactivé (9 007 199 254 740 992) ou activé (9 007 199 254 740 994); donc à ce stade, nous avons commencé à perdre de la précision même à l'échelle des nombres entiers (entiers). Ce qui a des implications pour notre boucle!

  4. Après avoir terminé la i = 9,007,199,254,740,992boucle, i++nous donne ... à i = 9,007,199,254,740,992nouveau; il n'y a pas de changement i, car le prochain entier ne peut pas être stocké et le calcul finit par arrondir vers le bas. ichangerait si nous le faisions i += 2, mais i++ne pouvons pas le changer. Nous avons donc atteint un état stable: ine change jamais et la boucle ne se termine jamais.

Voici les différents calculs pertinents:

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


79

Répondre:

La condition 1/i > 0sera toujours évaluée comme vraie:

  • Au départ, c'est vrai parce que cela 1/0évalue Infinityet Infinity > 0est vrai

  • Cela reste vrai puisque 1/i > 0c'est vrai pour tous i < Infinityet i++n'atteint jamais Infinity.

Pourquoi n'atteint i++jamais Infinity? En raison de la précision limitée du Numbertype de données, il existe une valeur pour laquelle i + 1 == i:

9007199254740992 + 1 == 9007199254740992 // true

Une fois iatteint cette valeur (qui correspond à ), il restera le même même après .Number.MAX_SAFE_INTEGER + 1i++

Nous avons donc une boucle infinie.


Appendice:

Pourquoi 9007199254740992 + 1 == 9007199254740992?

Le Numbertype de données JavaScript est en fait un flotteur double précision IEEE 754 64 bits . Chacun Numberest désassemblé et stocké en trois parties: signe de 1 bit, exposant de 11 bits et mantisse de 52 bits. Sa valeur est -1 signe × mantisse × 2 exposant .

Comment 9007199254740992 est- il représenté? Comme 1.0 × 2 53 , ou en binaire:

entrez la description de l'image ici

En incrémentant le bit le moins significatif de la mantisse, nous obtenons le nombre supérieur suivant:

entrez la description de l'image ici

La valeur de ce nombre est 1.00000000000000022… × 2 53 = 9007199254740994

Qu'est-ce que ça veut dire? Numberpeut être 900719925474099 2 ou 900719925474099 4 , mais rien entre les deux.

Maintenant, lequel choisirons-nous pour représenter 900719925474099 2 + 1 ? Les règles d'arrondi IEEE 754 donnent la réponse: 900719925474099 2 .


9
court et correct, mieux que la réponse actuellement acceptée
AlexWien

@AlexWien La réponse acceptée est une réponse acceptée par le wiki de la communauté.
fulvio

2
Je ne connais pas le terme "wiki communautaire accepté". Qu'est-ce que cela a à voir avec le stackoverflow? S'il s'agit d'un lien étranger, un lien doit être fourni. Les réponses acceptées sur stackoverflow peuvent toujours changer, le statut accepté n'est pas définitif.
AlexWien

"Pourquoi i ++ n'atteint jamais l'infini? En raison de la précision limitée du type de données Number ..." <- Il n'atteindra sûrement jamais l'infini, même avec un type de nombre à précision infinie. Vous savez, parce que vous ne pouvez pas compter jusqu'à infinity: P
Blorgbeard sort le

1
@Blorgbeard Vous pouvez compter jusqu'à l'infini avec des doubles de précision limitée, il vous suffit d'incrémenter d'un nombre beaucoup plus grand que 1, par exemple for (var i = 0; i < Infinity; i += 1E306);. Mais j'arrive d'où tu viens;)
le_m

27

La Number.MAX_SAFE_INTEGERconstante représente l'entier de sécurité maximal en JavaScript. La MAX_SAFE_INTEGERconstante a une valeur de 9007199254740991. Le raisonnement derrière ce nombre est que JavaScript utilise des nombres au format à virgule flottante double précision comme spécifié dans IEEE 754 et ne peut représenter en toute sécurité que des nombres compris entre - (2 53 - 1) et 2 53 - 1.

Sûr dans ce contexte fait référence à la capacité de représenter des nombres entiers exactement et de les comparer correctement. Par exemple, Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2évaluera à true, ce qui est mathématiquement incorrect. Voir Number.isSafeInteger()pour plus d'informations.

Comme il MAX_SAFE_INTEGERs'agit d'une propriété statique de Number, vous l'utilisez toujours Number.MAX_SAFE_INTEGERcomme propriété d'un Numberobjet que vous avez créé plutôt que comme propriété .

METTRE À JOUR:

Quelqu'un dans une réponse qui a été supprimée a mentionné: in'atteindra jamais l'infini. Une fois qu'il atteint Number.MAX_SAFE_INTEGER, i++n'incrémente plus la variable. Ce n'est en fait pas correct.

@TJ Crowder commentaires qui i = Number.MAX_SAFE_INTEGER; i++; i == Number.MAX_SAFE_INTEGER;est false. Mais la prochaine itération atteint un état inchangé, donc la réponse dans main est correcte.

idans l'exemple n'atteint jamais Infinity.


2
Plus précisément, 9007199254740992 + 1est 9007199254740992.
Kobi

1
@GerardoFurtado J'imagine que ce serait le cas.
fulvio

1
@GerardoFurtado for (var i=0; NaN > 0; i++) { console.log(i); }ne produira rien.
fulvio

2
@GerardoFurtado: Dans ce cas, la boucle s'arrêterait. Le corps de la boucle ne serait jamais entré du tout, puisque le tout premier test ( 1/i > 0) serait faux, puisque si iest 0, 1/iest NaNet NaN > 0est faux.
TJ Crowder

1
@TJCrowder J'ai mis à jour ma réponse. Merci d'avoir fait remarquer cela!
fulvio
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.