Чи припиняється це цикл «за», і чому / чому ні? для (var i = 0; 1 / i> 0; i ++) {}


104

Чи forзупиняється ця петля коли-небудь?

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

Якщо так, то коли і чому? Мені сказали, що це зупиняється, але мені це не було ніяких причин.

Оновлення

У рамках розслідування я написав досить довгу і детальну статтю, в якій пояснюється все, що відбувається під кришкою - ось що потрібно знати про тип номера JavaScript


5
Це не зупиниться. спробуйте виконати цей код коду. for (var i = 0; 1 / i> 0; i ++) {console.log (i)}
Sourabh Agrawal


3
Number.MAX_VALUE + 9.979202e291 == "Нескінченність" і 1 / (NaN або "Нескінченність" або "Не визначено")> 0 == false.
аскети

6
Чи ігнорує Javascript цей цикл, оскільки він не має внутрішніх заяв? тобто оптимізувати його далеко? Я знаю, що є кілька складених мов.
Брайан J

3
@askeet, як зазначено нижче та інші, ми ніколи не доходимо до Нескінченності, збільшуючись повторно, замість цього потрапляючи в петлю після Number.MAX_SAFE_INTEGER + 1.
LSpice

Відповіді:


128

(Я не прихильник мета-контенту, але: відповіді gotnull і le_m є і правильними, і корисними. Спочатку вони були, і навіть більше стосуються редакцій, зроблених після публікації цього спільноти Wiki. Оригінальна мотивація цього CW В основному це було відхилено в результаті цих редагувань, але воно залишається корисним, тому ... Також: Хоча є лише декілька авторів, перелічені багато інших членів спільноти дуже допомогли в коментарях, які були складені та очищені. не є лише ім'ям CW.)


Цикл не зупинятиметься в правильно реалізованому механізмі JavaScript. (Власне середовище двигуна може врешті-решт припинити його, оскільки це нескінченно, але це інша річ.)

Ось чому:

  1. Спочатку, коли iє 0, умова 1/i > 0справжня, тому що в JavaScript 1/0є Infinity, і Infinity > 0є правдою.

  2. Після цього iбуде збільшуватися та продовжуватиме рости як додатне ціле значення протягом тривалого часу (ще 9 007,199,254,740,991 ітерації). У всіх цих випадках 1/iзалишиться > 0(хоча значення для 1/iотримання дійсно малі до кінця!), Тому цикл продовжується аж до циклу, де iдосягає значення Number.MAX_SAFE_INTEGER.

  3. Номери в JavaScript - це двоканальна плаваюча точка з двома точністю IEEE-754, досить компактний формат (64 біт), який забезпечує швидкі обчислення та широкий діапазон. Це робиться, зберігаючи число як бітовий знак, 11-бітний показник і 52-розрядне значення (хоча завдяки кмітливості він фактично отримує 53 біт точності). Це двійкова плаваюча точка (основа 2): Значення (плюс деяка кмітливість) дає нам значення, а показник дає нам величину числа.

    Звичайно, маючи так багато значущих бітів, не кожне число може бути збережене. Ось число 1 і наступне найбільше число після 1, яке може зберігати формат, 1 + 2 -52 ≈ 1.00000000000000022, а наступне найвище після цього 1 + 2 × 2 -52 ≈ 1.00000000000000044:

       + ------------------------------------------------- -------------- знак біт
      / + ------- + ---------------------------------------- -------------- показник
     / / | + ------------------------------------------------- + - значущеі
    / / | / |
    0 01111111111 000000000000000000000000000000000000000000000000000000000000
                    = 1
    0 01111111111 000000000000000000000000000000000000000000000000000000000001
                    ≈ 1.00000000000000022
    0 01111111111 000000000000000000000000000000000000000000000000000000000010
                    ≈ 1.00000000000000044
    

    Зверніть увагу на стрибок з 1.00000000000000022 до 1.00000000000000044; немає можливості зберігати 1.0000000000000003. Це може трапитися з цілими числами, теж: Number.MAX_SAFE_INTEGER(9,007,199,254,740,991) є найвищим позитивним цілим значенням , що формат може містити де iі i + 1обидва точно представима ( специфікація ). Як 9,007,199,254,740,991, так і 9,007,199,254,740,992 можна представити, але наступне ціле число, 9,007,199,254,740,993, не може; наступне ціле число, яке ми можемо представити після 9,007,199,254,740,992, становить 9,007,199,254,740,994. Ось бітові шаблони, зверніть увагу на крайній правий (найменш значущий) біт:

       + ------------------------------------------------- -------------- знак біт
      / + ------- + ---------------------------------------- -------------- показник
     / / | + ------------------------------------------------- + - значущеі
    / / | / |
    0 10000110011 111111111111111111111111111111111111111111111111111111
                    = 9007199254740991 (Кількість.MAX_SAFE_INTEGER)
    0 10000110100 00000000000000000000000000000000000000000000000000000000
                    = 9007199254740992 (Кількість.MAX_SAFE_INTEGER + 1)
    x xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                      9007199254740993 (Number.MAX_SAFE_INTEGER + 2) не можна зберігати
    0 10000110100 00000000000000000000000000000000000000000000000000000001
                    = 9007199254740994 (Кількість.MAX_SAFE_INTEGER + 3)
    

    Пам'ятайте, що формат - це база 2, і з цим показником найменш значущий біт вже не дробовий; вона має значення 2. Може бути вимкнено (9,007,199,254,740,992) або увімкнено (9,007,199,254,740,994); тож у цей момент ми почали втрачати точність навіть за цілою шкалою чисел. Що має значення для нашого циклу!

  4. Закінчивши i = 9,007,199,254,740,992цикл, i++дає нам ... i = 9,007,199,254,740,992знову; немає змін i, оскільки наступне ціле число не може бути збережене, і обчислення закінчується округленням вниз. iзмінився б, якби ми i += 2, але i++не можемо змінити. Отже, ми дійшли до стаціонарного стану: iніколи не змінюється, і цикл ніколи не припиняється.

Ось різні відповідні розрахунки:

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

Відповідь:

Умова 1/i > 0завжди оцінюється як справжня:

  • Спочатку це правда, тому що 1/0оцінює Infinityі Infinity > 0є правдою

  • Це залишається істинним, оскільки 1/i > 0є правдою для всіх i < Infinityі i++ніколи не досягає Infinity.

Чому i++ніколи не доходить Infinity? Через обмежену точність Numberтипу даних існує значення, для якого i + 1 == i:

9007199254740992 + 1 == 9007199254740992 // true

Як тільки iдосягне цього значення (яке відповідає ), воно залишатиметься незмінним навіть після .Number.MAX_SAFE_INTEGER + 1i++

Тому у нас є нескінченна петля.


Додаток:

Чому так 9007199254740992 + 1 == 9007199254740992?

Тип Numberданих JavaScript - це фактично 64-бітний поплавок подвійної точності IEEE 754 . Кожна Numberчастина розбирається і зберігається у вигляді трьох частин: 1-бітний знак, 11-бітний показник і 52-бітна мантіса. Його значення -1 знак × мантиса × 2 показник .

Як представлено 9007199254740992 ? Як 1,0 × 2 53 , або у двійковій формі:

введіть тут опис зображення

Збільшуючи найменш значущий біт мантіси, ми отримуємо наступне вище число:

введіть тут опис зображення

Значення цього числа становить 1.00000000000000022… × 2 53 = 9007199254740994

Що це означає? Numberможе бути 900719925474099 2 або 900719925474099 4 , але між ними нічого.

Тепер, який із них ми обрали для представлення 900719925474099 2 + 1 ? Правила округлення IEEE 754 дають відповідь: 900719925474099 2 .


9
коротка і правильна, краща за прийняту сьогодні відповідь
AlexWien

@AlexWien Прийнята відповідь - це прийнята відповідь у вікі спільноти.
фульвіо

2
Я не знаю відповіді на термін "спільнота з вікі". Що це стосується stackoverflow? Якщо це іноземне посилання, слід надати посилання. Прийняті відповіді на stackoverflow завжди можуть змінюватися, статус, що приймається, не є остаточним.
AlexWien

"Чому я ++ ніколи не досягає нескінченності? Через обмежену точність типу даних про число ..." <- Безумовно, він ніколи не досягне нескінченності, навіть із нескінченним точним числом типу. нескінченність: P
Blorgbeard вийшов

1
@Blorgbeard Ви можете розраховувати на нескінченність з обмеженою точністю вдвічі, вам потрібно збільшити набагато більшу кількість, ніж 1, наприклад for (var i = 0; i < Infinity; i += 1E306);. Але я дістаюсь, звідки ти родом;)
le_m

27

Number.MAX_SAFE_INTEGERКонстанта являє собою максимальне безпечне число в JavaScript. MAX_SAFE_INTEGERКонстанта має значення 9007199254740991. Обґрунтування цього числа полягає в тому, що JavaScript використовує подвійну точність цифр формату з плаваючою комою, як зазначено в IEEE 754, і може безпечно представляти числа між - (2 53 - 1) і 2 53 - 1.

Безпечний у цьому контексті означає здатність точно представляти цілі числа та правильно їх порівнювати. Наприклад, Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2буде оцінювати до true, що математично неправильно. Див. Для Number.isSafeInteger()отримання додаткової інформації.

Оскільки MAX_SAFE_INTEGERстатична властивість Number, ви завжди використовуєте його як Number.MAX_SAFE_INTEGER, а не як властивість Numberствореного вами об'єкта.

ОНОВЛЕННЯ:

Хтось у відповіді, яку було видалено, згадував: iніколи не досягне нескінченності. Як тільки вона досягне Number.MAX_SAFE_INTEGER, i++більше не збільшує змінну. Це насправді не правильно.

@TJ Crowder коментує, що i = Number.MAX_SAFE_INTEGER; i++; i == Number.MAX_SAFE_INTEGER;є false. Але наступна ітерація досягає незмінного стану, тому головна відповідь правильна.

iу приклад ніколи не доходить Infinity.


2
Зокрема, 9007199254740992 + 1є 9007199254740992.
Кобі

1
@GerardoFurtado Я думаю, що це було б.
фульвіо

1
@GerardoFurtado for (var i=0; NaN > 0; i++) { console.log(i); }нічого не призведе.
фульвіо

2
@GerardoFurtado: У цьому випадку цикл зупиниться. Тіло циклу взагалі ніколи не буде введено, оскільки перший тест ( 1/i > 0) був би помилковим, оскільки якщо iє 0, то 1/iє NaNі NaN > 0є хибним.
TJ Crowder

1
@TJCrowder Я оновив свою відповідь. Дякуємо, що вказали на це!
фульвіо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.