Stoppt diese 'for'-Schleife und warum / warum nicht? für (var i = 0; 1 / i> 0; i ++) {}

104

Stoppt diese forSchleife jemals?

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

Wenn ja, wann und warum? Mir wurde gesagt, dass es aufhört, aber mir wurde kein Grund dafür gegeben.

Upddate

Als Teil der Untersuchung habe ich einen ziemlich langen und detaillierten Artikel geschrieben, der alles erklärt, was unter der Haube vor sich geht. Hier ist, was Sie über den Zahlentyp von JavaScript wissen müssen

Max Koretskyi
quelle
5
Es wird nicht aufhören. Versuchen Sie, dieses Stück Code auszuführen. für (var i = 0; 1 / i> 0; i ++) {console.log (i)}
Sourabh Agrawal
3
Number.MAX_VALUE + 9.979202e291 == "Infinity" und 1 / (NaN oder 'Infinity' oder 'undefined')> 0 == false.
Askeet
6
Wird Javascript diese Schleife ignorieren, weil sie keine Anweisungen enthält? dh es weg optimieren? Ich weiß, dass es einige kompilierte Sprachen gibt, die das tun würden.
Brian J
3
@askeet, wie gotnull und andere weiter unten hervorheben, erreichen wir Infinity nie durch wiederholtes Inkrementieren, sondern geraten danach in eine Schleife Number.MAX_SAFE_INTEGER + 1.
LSpice

Antworten:

128

(Ich bin kein Fan von Meta-Inhalten, aber: Die Antworten von gotnull und le_m sind korrekt und nützlich. Sie waren ursprünglich und noch mehr mit den Änderungen, die nach der Veröffentlichung dieses Community-Wikis vorgenommen wurden. Die ursprüngliche Motivation für dieses CW ist aufgrund dieser Änderungen größtenteils verschwunden, aber es bleibt nützlich, also ... Auch: Während nur ein paar Autoren aufgelistet sind, haben viele andere Community-Mitglieder bei Kommentaren, die eingeklappt und bereinigt wurden, sehr geholfen ist nicht nur ein CW im Namen.)


Die Schleife wird in einer korrekt implementierten JavaScript-Engine nicht gestoppt. (Die Host-Umgebung der Engine beendet sie möglicherweise, weil sie endlos ist, aber das ist eine andere Sache.)

Hier ist der Grund:

  1. Wenn dies der Fall iist 0, ist die Bedingung zunächst 1/i > 0wahr, da in JavaScript 1/0ist Infinityund Infinity > 0wahr ist.

  2. Danach iwird es erhöht und wächst noch lange als positiver ganzzahliger Wert weiter (weitere 9.007.199.254.740.991 Iterationen). In allen diesen Fällen 1/ibleibt > 0(auch wenn die Werte für 1/ibekommen wirklich gegen Ende klein!) Und so die Schleife bis einschließlich der Schleife weiter , wo ider Wert erreicht Number.MAX_SAFE_INTEGER.

  3. Zahlen in JavaScript sind binäre Gleitkommazahlen mit doppelter Genauigkeit nach IEEE-754, ein ziemlich kompaktes Format (64 Bit), das schnelle Berechnungen und einen großen Bereich ermöglicht. Dazu wird die Zahl als Vorzeichenbit, 11-Bit-Exponent und 52-Bit-Signifikand gespeichert (obwohl sie durch Cleverness tatsächlich 53 Bit Genauigkeit erhält). Es ist ein binärer Gleitkomma (Basis 2): Der Signifikand (plus etwas Klugheit) gibt uns den Wert und der Exponent gibt uns die Größe der Zahl.

    Natürlich kann mit nur so vielen signifikanten Bits nicht jede Zahl gespeichert werden. Hier ist die Zahl 1 und die nächsthöhere Zahl nach 1, die das Format speichern kann, 1 + 2 -52 ≈ 1.00000000000000022 und die nächsthöhere Zahl danach 1 + 2 × 2 -52 ≈ 1.00000000000000044:

       + --------------------------------------------- -------------- Zeichenbit
      / + ------- + ------------------------------------ -------------- Exponent
     // | + --------------------------------------------- + - Bedeutung
    // | / |
    0 01111111111 00000000000000000000000000000000000000000000000000000000
                    = 1
    0 01111111111 000000000000000000000000000000000000000000000000000001
                    ≈ 1.00000000000000022
    0 01111111111 000000000000000000000000000000000000000000000000000010
                    ≈ 1.00000000000000044
    

    Beachten Sie den Sprung von 1.00000000000000022 auf 1.00000000000000044; Es gibt keine Möglichkeit, 1.0000000000000003 zu speichern. Das kann mit ganzen Zahlen passieren, auch: Number.MAX_SAFE_INTEGER(9,007,199,254,740,991) ist die höchste positive Zahl , dass das Format halten kann , wo iund i + 1sind beide exakt darstellbar ( spec ). Sowohl 9.007.199.254.740.991 als auch 9.007.199.254.740.992 können dargestellt werden, die nächste Ganzzahl, 9.007.199.254.740.993, jedoch nicht. Die nächste Ganzzahl, die wir nach 9.007.199.254.740.992 darstellen können, ist 9.007.199.254.740.994. Hier sind die Bitmuster. Beachten Sie das Bit ganz rechts (niedrigstwertig):

       + --------------------------------------------- -------------- Zeichenbit
      / + ------- + ------------------------------------ -------------- Exponent
     // | + --------------------------------------------- + - Bedeutung
    // | / |
    0 10000110011 111111111111111111111111111111111111111111111111111111
                    = 9007199254740991 (Number.MAX_SAFE_INTEGER)
    0 10000110100 000000000000000000000000000000000000000000000000000000
                    = 9007199254740992 (Number.MAX_SAFE_INTEGER + 1)
    x xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                      9007199254740993 (Number.MAX_SAFE_INTEGER + 2) kann nicht gespeichert werden
    0 10000110100 000000000000000000000000000000000000000000000000000001
                    = 9007199254740994 (Number.MAX_SAFE_INTEGER + 3)
    

    Denken Sie daran, dass das Format Basis 2 ist und mit diesem Exponenten das niedrigstwertige Bit nicht mehr gebrochen ist. es hat einen Wert von 2. Es kann aus (9.007.199.254.740.992) oder ein (9.007.199.254.740.994) sein; Zu diesem Zeitpunkt haben wir begonnen, die Genauigkeit selbst auf der ganzen Zahlenskala (Ganzzahlskala) zu verlieren. Was Auswirkungen auf unsere Schleife hat!

  4. Nach Abschluss der i = 9,007,199,254,740,992Schleife i++gibt uns ... i = 9,007,199,254,740,992wieder; Es gibt keine Änderung in i, da die nächste Ganzzahl nicht gespeichert werden kann und die Berechnung abgerundet wird. iwürde sich ändern, wenn wir es tun würden i += 2, i++kann es aber nicht ändern. Wir haben also den stationären Zustand erreicht: iÄndert sich nie und die Schleife endet nie.

Hier sind die verschiedenen relevanten Berechnungen:

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
quelle
79

Antworten:

Die Bedingung 1/i > 0wird immer als wahr bewertet:

  • Anfangs ist es wahr, weil es 1/0bewertet Infinityund Infinity > 0wahr ist

  • Es bleibt wahr, da 1/i > 0es für alle gilt i < Infinityund i++nie erreicht Infinity.

Warum erreicht i++nie Infinity? Aufgrund der begrenzten Genauigkeit des NumberDatentyps gibt es einen Wert, für den i + 1 == i:

9007199254740992 + 1 == 9007199254740992 // true

Sobald idieser Wert erreicht ist (der entspricht ), bleibt er auch danach gleich .Number.MAX_SAFE_INTEGER + 1i++

Wir haben also eine Endlosschleife.


Blinddarm:

Warum ist 9007199254740992 + 1 == 9007199254740992?

Der NumberDatentyp von JavaScript ist ein 64-Bit- Float mit doppelter Genauigkeit nach IEEE 754 . Jedes Numberwird zerlegt und als drei Teile gespeichert: 1-Bit-Vorzeichen, 11-Bit-Exponent und 52-Bit-Mantisse. Sein Wert ist -1 Vorzeichen × Mantisse × 2 Exponent .

Wie ist 9007199254740992 dargestellt? Als 1,0 × 2 53 oder binär:

Geben Sie hier die Bildbeschreibung ein

Wenn wir das niedrigstwertige Bit der Mantisse erhöhen, erhalten wir die nächsthöhere Zahl:

Geben Sie hier die Bildbeschreibung ein

Der Wert dieser Nummer ist 1.00000000000000022… × 2 53 = 9007199254740994

Was bedeutet das? Numberkann entweder 900719925474099 2 oder 900719925474099 4 sein , aber nichts dazwischen.

Welches sollen wir nun wählen, um 900719925474099 2 + 1 darzustellen ? Die Rundungsregeln nach IEEE 754 geben die Antwort: 900719925474099 2 .

le_m
quelle
9
kurz und richtig, besser als die aktuell akzeptierte Antwort
AlexWien
@AlexWien Die akzeptierte Antwort ist eine vom Community-Wiki akzeptierte Antwort.
Fulvio
2
Ich kenne die Antwort "Community Wiki Accpeted" nicht. Was hat das mit Stackoverflow zu tun? Wenn dies ein fremder Link ist, sollte ein Link bereitgestellt werden. Akzeptierte Antworten zum Stackoverflow können sich jederzeit ändern, der Status ist nicht endgültig.
AlexWien
"Warum erreicht i ++ niemals die Unendlichkeit? Aufgrund der begrenzten Genauigkeit des Number-Datentyps ..." <- Sicherlich würde es selbst mit einem Zahlentyp mit unendlicher Genauigkeit niemals die Unendlichkeit erreichen. Sie wissen, weil Sie nicht zählen können unendlich: P
Blorgbeard ist am
1
@Blorgbeard Sie können mit begrenzter Genauigkeit bis zur Unendlichkeit zählen. Sie müssen nur um eine viel größere Zahl als 1 erhöhen , z for (var i = 0; i < Infinity; i += 1E306);. Aber ich komme dahin, woher du kommst;)
le_m
27

Die Number.MAX_SAFE_INTEGERKonstante repräsentiert die maximale sichere Ganzzahl in JavaScript. Die MAX_SAFE_INTEGERKonstante hat einen Wert von 9007199254740991. Der Grund für diese Zahl ist, dass JavaScript Gleitkomma-Zahlen mit doppelter Genauigkeit verwendet, wie in IEEE 754 angegeben, und nur Zahlen zwischen - (2 53 - 1) und 2 53 - 1 sicher darstellen kann .

Sicher bezieht sich in diesem Zusammenhang auf die Fähigkeit, Ganzzahlen genau darzustellen und korrekt zu vergleichen. Zum Beispiel Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2wird ausgewertet true, was mathematisch falsch ist. Siehe Number.isSafeInteger()für weitere Informationen.

Da MAX_SAFE_INTEGERes sich um eine statische Eigenschaft von handelt Number, verwenden Sie sie immer als Number.MAX_SAFE_INTEGERund nicht als Eigenschaft eines von NumberIhnen erstellten Objekts.

AKTUALISIEREN:

Jemand in einer Antwort, die gelöscht wurde, erwähnte: iwird niemals unendlich erreichen. Sobald es erreicht ist Number.MAX_SAFE_INTEGER, i++wird die Variable nicht mehr erhöht. Dies ist in der Tat nicht korrekt.

@TJ Crowder kommentiert das i = Number.MAX_SAFE_INTEGER; i++; i == Number.MAX_SAFE_INTEGER;heißt false. Die nächste Iteration erreicht jedoch einen unveränderlichen Zustand, sodass die Hauptantwort richtig ist.

iim Beispiel nie erreicht Infinity.

Fulvio
quelle
2
Insbesondere 9007199254740992 + 1ist 9007199254740992.
Kobi
1
@ GerardoFurtado Ich würde es mir vorstellen.
Fulvio
1
@ GerardoFurtado for (var i=0; NaN > 0; i++) { console.log(i); }wird nichts produzieren.
Fulvio
2
@ GerardoFurtado: In diesem Fall würde die Schleife anhalten. Der Schleifenkörper würde überhaupt nicht eingegeben werden, seit dem ersten Test ( 1/i > 0) würde falsch sein, denn wenn iist 0, 1/iist NaN, und NaN > 0ist falsch.
TJ Crowder
1
@TJCrowder Ich habe meine Antwort aktualisiert. Vielen Dank für den Hinweis!
Fulvio