【问题标题】:Does this 'for' loop stop, and why/why not? for (var i=0; 1/i > 0; i++) { }这个“for”循环是否停止,为什么/为什么不停止? for (var i=0; 1/i > 0; i++) { }
【发布时间】:2016-10-16 01:48:00
【问题描述】:

这个for 循环会停止吗?

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

如果是这样,何时以及为什么?有人告诉我它会停止,但我没有理由这样做。

更新

作为调查的一部分,我写了相当长而详细的文章,解释了幕后发生的一切 - Here is what you need to know about JavaScript’s Number type

【问题讨论】:

  • 它不会停止。尝试执行这段代码。 for (var i=0; 1/i > 0; i++) { console.log(i) }
  • Number.MAX_VALUE + 9.979202e291 == "Infinity" and 1/ (NaN or 'Infinity' or 'undefined') > 0 == false.
  • Javascript 会因为里面没有语句而忽略该循环吗?即优化它?我知道有一些编译语言可以。
  • @askeet,正如gotnull 和其他人在下面指出的那样,我们永远不会通过重复递增达到无穷大,而是在Number.MAX_SAFE_INTEGER + 1 之后陷入循环。

标签: javascript


【解决方案1】:

(我不是元内容的粉丝,但是:gotnull'sle_m's 的答案都是正确且有用的。它们最初是,并且更是如此在发布此社区 Wiki 后进行的编辑。由于这些编辑,此 CW 的最初动机在很大程度上已经消失,但它仍然有用,所以... 另外:虽然只列出了几个作者,但还有许多其他作者社区成员对已折叠和清理的 cmets 帮助很大。这不仅仅是名义上的 CW。)


循环不会在正确实现的 JavaScript 引擎中停止。 (引擎的宿主环境可能最终会终止它,因为它是无止境的,但这是另一回事。)

原因如下:

  1. 最初,当i0 时,条件1/i > 0 为真,因为在JavaScript 中,1/0Infinity,而Infinity > 0 为真。

  2. 之后,i 将递增并在很长一段时间内作为正整数值继续增长(进一步的 9,007,199,254,740,991 次迭代)。在所有这些情况下,1/i 将保持为> 0(尽管1/i 的值在接近尾声时真的变小了!),因此循环一直持续到并包括@ 987654334@ 达到Number.MAX_SAFE_INTEGER 的值。

  3. JavaScript 中的数字是 IEEE-754 双精度二进制浮点数,这是一种相当紧凑的格式(64 位),可提供快速计算和广泛的范围。它通过将数字存储为符号位、11 位指数和 52 位有效数来实现这一点(尽管通过聪明它实际上获得了 53 位精度)。它是二进制(以 2 为底)浮点数:有效数(加上一些巧妙之处)为我们提供了值,而指数为我们提供了数字的大小。

    当然,只有这么多有效位,并不是每个数字都可以存储。这是数字 1,格式可以存储的下一个最大数字,1 + 2-52 ≈ 1.00000000000000022,然后是 1 + 2 × 2-52 ≈ 1.00000000000000044:

    +-------------------------------------------------- -------------- 符号位 / +-------+---------------------------------------- -------------- 指数 // | +-------------------------------------------------- +- 有效数字 // | / | 0 01111111111 000000000000000000000000000000000000000000000000000000 = 1 0 01111111111 00000000000000000000000000000000000000000000000000001 ≈ 1.00000000000000022 0 01111111111 0000000000000000000000000000000000000000000000000010 ≈ 1.00000000000000044

    注意从 1.00000000000000022 到 1.00000000000000044 的跳跃;没有办法存储 1.0000000000000003。整数也可能发生这种情况:Number.MAX_SAFE_INTEGER (9,007,199,254,740,991) 是格式可以容纳的最大正整数值,其中ii + 1 都可以精确表示(spec)。 9,007,199,254,740,991 和 9,007,199,254,740,992 都可以表示,但是 next 整数 9,007,199,254,740,993 不能;我们可以在 9,007,199,254,740,992 之后表示的下一个整数是 9,007,199,254,740,994。以下是位模式,注意最右边(最低有效)位:

    +-------------------------------------------------- -------------- 符号位 / +-------+---------------------------------------- -------------- 指数 // | +-------------------------------------------------- +- 有效数字 // | / | 0 10000110011 111111111111111111111111111111111111111111111111111 = 9007199254740991 (Number.MAX_SAFE_INTEGER) 0 10000110100 00000000000000000000000000000000000000000000000000000 = 9007199254740992 (Number.MAX_SAFE_INTEGER + 1) x xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 9007199254740993 (Number.MAX_SAFE_INTEGER + 2) 无法存储 0 10000110100 00000000000000000000000000000000000000000000000000001 = 9007199254740994 (Number.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,992i 没有变化,因为无法存储下一个整数并且计算最终会向下舍入。如果我们做i += 2i 会改变,但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

【讨论】:

    【解决方案2】:

    答案:

    条件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 + 1),即使在i++ 之后也将保持不变。

    因此我们有一个无限循环。


    附录:

    为什么是9007199254740992 + 1 == 9007199254740992

    JavaScript 的 Number 数据类型实际上是一个 64 位的 IEEE 754 double precision float。每个Number 被反汇编并存储为三个部分:1 位符号、11 位指数和 52 位尾数。其值为-1符号×尾数×2指数

    9007199254740992如何表示? 1.0 × 2 53,或二进制:

    增加尾数的最低有效位,我们得到下一个更高的数字:

    这个数字的值是 1.00000000000000022…×2 53 = 9007199254740994

    这是什么意思? Number 可以是 9007199254740992 或 9007199254740994,但不能介于两者之间。

    现在,我们应该选择哪一个来表示 9007199254740992 + 1IEEE 754 rounding rules 给出答案:9007199254740992

    【讨论】:

    • 简短而正确,比当前接受的答案更好
    • @AlexWien 接受的答案是社区 wiki 接受的答案。
    • 我不知道“社区 wiki 接受”这个词的答案。这与stackoverflow有什么关系?如果这是外部链接,则应提供链接。 stackoverflow 上接受的答案总是可以改变,接受的状态不是最终的。
    • “为什么 i++ 永远不会达到无穷大?由于 Number 数据类型的精度有限......” ,因为你不能数到无穷大:P
    • @Blorgbeard 您可以以有限精度双精度数计数到 Infinity,您只需增加比 1 大得多的数字,例如。 G。 for (var i = 0; i < Infinity; i += 1E306);。但我知道你来自哪里;)
    【解决方案3】:

    Number.MAX_SAFE_INTEGER 常量表示 JavaScript 中的最大安全整数。 MAX_SAFE_INTEGER 常量的值为 9007199254740991。该数字背后的原因是 JavaScript 使用 double-precision floating-point format numbers 中指定的 IEEE 754 并且只能安全地表示 -(253 - 1) 和 253 - 之间的数字1.

    在这种情况下,安全是指准确表示整数并正确比较它们的能力。例如,Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 将计算为 true,这在数学上是不正确的。请参阅Number.isSafeInteger() 了解更多信息。

    因为MAX_SAFE_INTEGERNumber 的静态属性,所以您始终将其用作Number.MAX_SAFE_INTEGER,而不是您创建的Number 对象的属性。

    更新:

    有人在已删除的答案中提到:i 永远不会达到无穷大。一旦达到Number.MAX_SAFE_INTEGERi++ 就不再增加变量。这实际上是正确的。

    @T.J.克劳德认为i = Number.MAX_SAFE_INTEGER; i++; i == Number.MAX_SAFE_INTEGER;false。但是下一次迭代达到了不变的状态,所以main中的答案是正确的。

    示例中的i 永远不会到达Infinity

    【讨论】:

    • 具体来说,9007199254740992 + 19007199254740992
    • @GerardoFurtado 我想会的。
    • @GerardoFurtado for (var i=0; NaN > 0; i++) { console.log(i); } 不会产生任何东西。
    • @GerardoFurtado:在这种情况下,循环将停止。永远不会输入循环体,因为第一个测试 (1/i > 0) 将是错误的,因为如果 i01/iNaNNaN > 0 是错误的。
    • @T.J.Crowder 我已经更新了我的答案。感谢您指出这一点!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-14
    • 1970-01-01
    • 1970-01-01
    • 2014-08-09
    • 1970-01-01
    • 2011-07-27
    • 2015-11-29
    相关资源
    最近更新 更多