Czy ta pętla „for” zatrzymuje się i dlaczego / dlaczego nie? dla (var i = 0; 1 / i> 0; i ++) {}


104

Czy ta forpętla kiedykolwiek się zatrzyma?

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

Jeśli tak, to kiedy i dlaczego? Powiedziano mi, że to się kończy, ale nie podano mi powodu.

Aktualizacja

W ramach śledztwa napisałem dość długi i szczegółowy artykuł, który wyjaśnia wszystko, co dzieje się pod maską - Oto, co musisz wiedzieć o typie liczby w JavaScript


5
To się nie skończy. spróbuj wykonać ten fragment kodu. for (var i = 0; 1 / i> 0; i ++) {console.log (i)}
Sourabh Agrawal


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

6
Czy JavaScript zignoruje tę pętlę, ponieważ nie ma w niej żadnych instrukcji? tj. zoptymalizować go? Wiem, że jest kilka języków kompilowanych, które by to zrobiły.
Brian J

3
@askeet, jak gotnull i inni wskazują poniżej, nigdy nie osiągamy Nieskończoności przez wielokrotne zwiększanie wartości, zamiast tego wpadamy w pętlę Number.MAX_SAFE_INTEGER + 1.
LSpice

Odpowiedzi:


128

(Nie jestem fanem meta-treści, ale: odpowiedzi gotnull i le_m są zarówno poprawne, jak i użyteczne. Były pierwotnie, a tym bardziej, jeśli chodzi o zmiany wprowadzone po opublikowaniu tego Wiki społeczności. Oryginalna motywacja do tego CW w dużej mierze zniknął w wyniku tych zmian, ale pozostaje przydatny, więc ... Ponadto: Chociaż na liście jest tylko kilku autorów, wielu innych członków społeczności bardzo pomogło w tworzeniu komentarzy, które zostały złożone i poprawione. to nie tylko CW z nazwy.)


Pętla nie zatrzymuje się w poprawnie zaimplementowanym silniku JavaScript. (Środowisko hosta silnika może ostatecznie go zakończyć, ponieważ jest nieskończone, ale to inna sprawa).

Dlatego:

  1. Początkowo, gdy ijest 0, warunek 1/i > 0jest prawdziwy, ponieważ w JavaScript 1/0jest Infinityi Infinity > 0jest prawdziwy.

  2. Następnie iprzez długi czas będzie zwiększany i nadal rośnie jako dodatnia liczba całkowita (kolejne 9 007 199 254 740 991 iteracji). We wszystkich tych przypadkach 1/ipozostanie > 0(chociaż wartości 1/istają się naprawdę małe pod koniec!), Więc pętla jest kontynuowana aż do pętli, w której iosiągnie wartość Number.MAX_SAFE_INTEGER.

  3. Liczby w JavaScript to binarne zmiennoprzecinkowe podwójnej precyzji IEEE-754, dość kompaktowy format (64 bity), który zapewnia szybkie obliczenia i szeroki zakres. Czyni to poprzez przechowywanie liczby jako bitu znaku, 11-bitowego wykładnika i 52-bitowego znaczenia (chociaż dzięki sprytowi faktycznie uzyskuje 53 bity precyzji). Jest to liczba zmiennoprzecinkowa binarna (podstawa 2): Mantyga (plus trochę sprytu) daje nam wartość, a wykładnik potęgi podaje wielkość liczby.

    Oczywiście przy tak wielu znaczących bitach nie każda liczba może zostać zapisana. Oto numer 1, a następny najwyższy numer po 1, że format może przechowywać, 1 + 2 -52 ≈ +1,00000000000000022, a następnego najwyższy po tym 1 + 2 x 2 -52 ≈ 1.00000000000000044:

       + ------------------------------------------------- -------------- znak bitu
      / + ------- + ---------------------------------------- -------------- wykładnik
     / / | + ------------------------------------------------- + - znacznik
    / / | / |
    0 01111111111 00000000000000000000000000000000000000000000000000
                    = 1
    0 01111111111 00000000000000000000000000000000000000000000000001
                    ≈ 1,00000000000000022
    0 01111111111 00000000000000000000000000000000000000000000000010
                    ≈ 1,00000000000000044
    

    Zwróć uwagę na skok od 1,00000000000000022 do 1,00000000000000044; nie ma sposobu na przechowywanie 1,0000000000000003. Co może się zdarzyć z liczb całkowitych, za: Number.MAX_SAFE_INTEGER(9,007,199,254,740,991) jest najwyższą wartością dodatnią liczbą całkowitą, że format może pomieścić gdzie ii i + 1są zarówno dokładnie reprezentowalna ( specyfikacja ). Można przedstawić zarówno 9 007 199 254 740 991, jak i 9 007 199 254 740 992, ale następna liczba całkowita 9 007 199 254 740 993 nie; następna liczba całkowita, którą możemy przedstawić po 9,007,199,254,740,992, to 9007,199,254,740,994. Oto wzory bitów, zwróć uwagę na najbardziej prawy (najmniej znaczący) bit:

       + ------------------------------------------------- -------------- znak bitu
      / + ------- + ---------------------------------------- -------------- wykładnik
     / / | + ------------------------------------------------- + - znacznik
    / / | / |
    0 10000110011 1111111111111111111111111111111111111111111111111111
                    = 9007199254740991 (Number.MAX_SAFE_INTEGER)
    0 10000110100 00000000000000000000000000000000000000000000000000
                    = 9007199254740992 (liczba.MAX_SAFE_INTEGER + 1)
    x xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                      Nie można zapisać 9007199254740993 (Number.MAX_SAFE_INTEGER + 2)
    0 10000110100 00000000000000000000000000000000000000000000000001
                    = 9007199254740994 (liczba.MAX_SAFE_INTEGER + 3)
    

    Pamiętaj, że format to podstawa 2, a przy tym wykładniku najmniej znaczący bit nie jest już ułamkowy; ma wartość 2. Może być wyłączony (9 007 199 254 740 992) lub włączony (9 007 199 254 740 994); więc w tym momencie zaczęliśmy tracić precyzję nawet na skali liczb całkowitych (całkowitej). Co ma wpływ na naszą pętlę!

  4. Po wykonaniu i = 9,007,199,254,740,992pętli i++daje nam ... i = 9,007,199,254,740,992znowu; nie ma zmiany i, ponieważ nie można zapisać następnej liczby całkowitej, a obliczenia kończą się zaokrągleniem w dół. izmieniłby się, gdybyśmy to zrobili i += 2, ale i++nie możemy tego zmienić. Osiągnęliśmy więc stan ustalony: inigdy się nie zmienia, a pętla nigdy się nie kończy.

Oto różne odpowiednie obliczenia:

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

Odpowiedź:

Warunek 1/i > 0zawsze będzie prawdziwy:

  • Początkowo jest to prawda, ponieważ 1/0wartościuje Infinityi Infinity > 0jest prawdziwe

  • Pozostaje prawdą, ponieważ 1/i > 0jest prawdą dla wszystkich i < Infinityi i++nigdy nie dociera Infinity.

Dlaczego i++nigdy nie dociera Infinity? Ze względu na ograniczoną precyzję Numbertypu danych istnieje wartość, dla której i + 1 == i:

9007199254740992 + 1 == 9007199254740992 // true

Gdy iosiągnie tę wartość (która odpowiada ), pozostanie taka sama nawet po .Number.MAX_SAFE_INTEGER + 1i++

Mamy zatem nieskończoną pętlę.


Dodatek:

Dlaczego tak jest 9007199254740992 + 1 == 9007199254740992?

Typ Numberdanych JavaScript to w rzeczywistości 64-bitowy zmiennoprzecinkowy IEEE 754 o podwójnej precyzji . Każdy Numberjest demontowany i przechowywany jako trzy części: 1-bitowy znak, 11-bitowy wykładnik i 52-bitowa mantysa. Jego wartość to -1 znak × mantysa × 2 wykładnik .

W jaki sposób reprezentowany jest 9007199254740992 ? Jak 1,0 × 2 53 lub binarnie:

wprowadź opis obrazu tutaj

Zwiększając najmniej znaczący kawałek mantysy, otrzymujemy następną wyższą liczbę:

wprowadź opis obrazu tutaj

Wartość tej liczby to 1,00000000000000022… × 2 53 = 9007199254740994

Co to znaczy? Numbermoże być 900719925474099 2 lub 900719925474099 4 , ale nie ma niczego pomiędzy.

Który z nich powinniśmy wybrać do reprezentowania 900719925474099 2 + 1 ? W IEEE 754 zasady zaokrąglania dać odpowiedź: 900719925474099 2 .


9
krótka i poprawna, lepsza niż obecnie akceptowana odpowiedź
AlexWien

@AlexWien Zaakceptowana odpowiedź to zaakceptowana odpowiedź społeczności wiki.
fulvio

2
Nie znam odpowiedzi na termin „Community wiki accpeted”. Co to ma wspólnego z przepełnieniem stosu? Jeśli jest to link zagraniczny, należy podać link. Zaakceptowane odpowiedzi na stackoverflow mogą zawsze ulec zmianie, stan zaakceptowany nie jest ostateczny.
AlexWien

„Dlaczego i ++ nigdy nie osiąga nieskończoności? Ze względu na ograniczoną precyzję typu danych Number…” <- Z pewnością nigdy nie osiągnie nieskończoności, nawet z typem liczbowym o nieskończonej precyzji. infinity: P
Blorgbeard wychodzi

1
@Blorgbeard Ty może liczyć do nieskończoności z ograniczoną dokładnością deblu, po prostu trzeba przyrost o znacznie większej ilości niż 1, np for (var i = 0; i < Infinity; i += 1E306);. Ale rozumiem, skąd pochodzisz;)
le_m

27

Number.MAX_SAFE_INTEGERStała reprezentuje maksymalną bezpieczną całkowitą w JavaScript. MAX_SAFE_INTEGERStała ma wartość 9007199254740991. Powodem tej liczby jest to, że JavaScript używa liczb zmiennoprzecinkowych o podwójnej precyzji, jak określono w IEEE 754 i może bezpiecznie reprezentować tylko liczby z przedziału od - (2 53 - 1) do 2 53 - 1.

Bezpieczne w tym kontekście odnosi się do możliwości dokładnego przedstawiania liczb całkowitych i ich poprawnego porównywania. Na przykład Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2oszacuje wartość true, co jest matematycznie niepoprawne. Zobacz, Number.isSafeInteger()aby uzyskać więcej informacji.

Ponieważ MAX_SAFE_INTEGERjest to właściwość statyczna programu Number, zawsze używasz jej jako Number.MAX_SAFE_INTEGER, a nie jako właściwości utworzonego Numberobiektu.

AKTUALIZACJA:

Ktoś w usuniętej odpowiedzi wspomniał: inigdy nie osiągnie nieskończoności. Gdy osiągnie Number.MAX_SAFE_INTEGER, i++nie zwiększa już wartości zmiennej. W rzeczywistości nie jest to poprawne.

@TJ Crowder komentuje i = Number.MAX_SAFE_INTEGER; i++; i == Number.MAX_SAFE_INTEGER;to false. Ale następna iteracja osiąga niezmienny stan, więc odpowiedź w zasadzie jest poprawna.

iw przykładzie nigdy nie dociera Infinity.


2
W szczególności 9007199254740992 + 1jest 9007199254740992.
Kobi

1
@GerardoFurtado Wyobrażam sobie, że tak.
fulvio

1
@GerardoFurtado for (var i=0; NaN > 0; i++) { console.log(i); }nic nie wyprodukuje.
fulvio

2
@GerardoFurtado: W takim przypadku pętla by się zatrzymała. Ciało pętli nigdy nie zostałoby wprowadzone, ponieważ pierwszy test ( 1/i > 0) byłby fałszem, ponieważ jeśli ijest 0, 1/ijest NaNi NaN > 0jest fałszem.
TJ Crowder

1
@TJCrowder Zaktualizowałem moją odpowiedź. Dziękuję za zwrócenie uwagi!
fulvio
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.