Останавливается ли этот цикл for и почему / почему нет? for (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)}
Сураб Агравал,
3
Number.MAX_VALUE + 9.979202e291 == "Бесконечность" и 1 / (NaN, или "Бесконечность", или "undefined")> 0 == false.
askeet
6
Будет ли Javascript игнорировать этот цикл, потому что внутри него нет операторов? т.е. оптимизировать подальше? Я знаю, что есть некоторые компилированные языки, которые могли бы это сделать.
Brian J
3
@askeet, как getnull и другие указывают ниже, мы никогда не достигаем Бесконечности путем многократного увеличения, вместо этого попадая в цикл после Number.MAX_SAFE_INTEGER + 1.
LSpice

Ответы:

128

(Я не поклонник метаконтента , но ответы : gotnull и le_m правильны и полезны. Они были изначально, а тем более с изменениями, внесенными после публикации этой вики сообщества. Исходная мотивация для этого 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 0000000000000000000000000000000000000000000000000000
                    = 1
    0 01111111111 0000000000000000000000000000000000000000000000000001
                    ≈ 1.00000000000000022
    0 01111111111 0000000000000000000000000000000000000000000000000010
                    ≈ 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 111111111111111111111111111111111111111111111111
                    = 9007199254740991 (Число.MAX_SAFE_INTEGER)
    0 10000110100 0000000000000000000000000000000000000000000000000000
                    = 9007199254740992 (Число.MAX_SAFE_INTEGER + 1)
    x xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                      9007199254740993 (Number.MAX_SAFE_INTEGER + 2) не может быть сохранен
    0 10000110100 0000000000000000000000000000000000000000000000000001
                    = 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

TJ Crowder
источник
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 .

le_m
источник
9
короткий и правильный, лучше, чем принятый в настоящее время ответ
AlexWien
@AlexWien Принятый ответ - это принятый вики-сообществом ответ.
fulvio
2
Я не знаю ответа на вопрос "одобрено сообществом вики". При чем здесь stackoverflow? Если это внешняя ссылка, ссылка должна быть предоставлена. Принятые ответы на stackoverflow всегда могут измениться, статус accpeted не является окончательным.
AlexWien
«Почему i ++ никогда не достигает бесконечности? Из-за ограниченной точности типа данных Number ...» <- Конечно, он никогда не достигнет бесконечности, даже с числовым типом бесконечной точности. Вы знаете, потому что вы не можете считать до infinity: 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, я бы мог подумать.
fulvio
1
@GerardoFurtado for (var i=0; NaN > 0; i++) { console.log(i); }ничего не будет производить.
fulvio
2
@GerardoFurtado: В этом случае цикл остановится. Тело цикла вообще не будет введено, поскольку самый первый test ( 1/i > 0) будет ложным, поскольку if iесть 0, 1/iесть NaNи NaN > 0ложно.
TJ Crowder
1
@TJCrowder Я обновил свой ответ. Спасибо что подметил это!
fulvio