Vòng lặp 'for' này có dừng lại không và tại sao / tại sao không? for (var i = 0; 1 / i> 0; i ++) {}


104

forVòng lặp này có bao giờ dừng lại không?

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

Nếu vậy, khi nào và tại sao? Tôi đã được thông báo rằng nó dừng lại, nhưng tôi không có lý do gì cho điều đó.

Cập nhật

Là một phần của cuộc điều tra, tôi đã viết một bài báo khá dài và chi tiết giải thích mọi thứ đang diễn ra - Đây là những gì bạn cần biết về loại Số của JavaScript


5
Nó sẽ không dừng lại. thử thực hiện đoạn mã này. for (var i = 0; 1 / i> 0; i ++) {console.log (i)}
Sourabh Agrawal


3
Number.MAX_VALUE + 9.979202e291 == "Infinity" và 1 / (NaN hoặc 'Infinity' hoặc 'undefined')> 0 == false.
askeet

6
Javascript sẽ bỏ qua vòng lặp đó vì nó không có câu lệnh nào bên trong? tức là Tối ưu hóa nó đi? Tôi biết có một số ngôn ngữ biên dịch sẽ.
Brian J

3
@askeet, như gotnull và những người khác chỉ ra bên dưới, chúng tôi không bao giờ đạt đến Vô cực bằng cách tăng liên tục, thay vào đó sẽ bị mắc kẹt sau đó Number.MAX_SAFE_INTEGER + 1.
LSpice

Câu trả lời:


128

(Tôi không phải là fan hâm mộ của meta-nội dung, nhưng: của gotnullle_m của câu trả lời đều chính xác và hữu ích Họ ban đầu, và đang có. Thậm chí nhiều hơn như vậy với các chỉnh sửa đưa ra sau khi cộng đồng Wiki này đã được đăng Động lực ban đầu cho CW này. Phần lớn đã biến mất do những chỉnh sửa đó, nhưng nó vẫn hữu ích, vì vậy ... Ngoài ra: Trong khi chỉ có một vài tác giả được liệt kê, nhiều thành viên cộng đồng khác đã giúp đỡ rất nhiều với các nhận xét đã được xếp lại và làm sạch. Điều này không chỉ là một CW trên danh nghĩa.)


Vòng lặp sẽ không dừng lại trong một công cụ JavaScript được triển khai chính xác. (Môi trường máy chủ của engine cuối cùng có thể chấm dứt nó vì nó vô tận, nhưng đó là một điều khác.)

Đây là lý do tại sao:

  1. Ban đầu, khi i0, điều kiện 1/i > 0là đúng bởi vì trong JavaScript, 1/0Infinity, và Infinity > 0là đúng.

  2. Sau đó, isẽ được tăng dần và tiếp tục phát triển như một giá trị nguyên dương trong một thời gian dài (thêm 9,007,199,254,740,991 lần lặp). Trong tất cả các trường hợp đó, 1/isẽ vẫn còn > 0(mặc dù các giá trị của 1/iget thực sự nhỏ về cuối!) Và do đó vòng lặp tiếp tục cho đến và bao gồm cả vòng lặp iđạt đến giá trị Number.MAX_SAFE_INTEGER.

  3. Các số trong JavaScript là dấu chấm động nhị phân độ chính xác kép IEEE-754, một định dạng khá nhỏ gọn (64 bit) cung cấp cho các phép tính nhanh và phạm vi rộng lớn. Nó thực hiện điều này bằng cách lưu trữ số dưới dạng bit dấu, số mũ 11 bit, và dấu nghĩa 52 bit (mặc dù thông qua sự thông minh, nó thực sự có được độ chính xác 53 bit). Đó là dấu phẩy động nhị phân (cơ số 2): Ý nghĩa và (cộng với một số thông minh) cho chúng ta giá trị và số mũ cho chúng ta độ lớn của số.

    Đương nhiên, chỉ với rất nhiều bit quan trọng, không phải mọi số đều có thể được lưu trữ. Đây là số 1 và số cao nhất tiếp theo sau số 1 mà định dạng có thể lưu trữ, 1 + 2 -52 ≈ 1.00000000000000022 và số cao nhất tiếp theo sau đó 1 + 2 × 2 -52 ≈ 1.00000000000000044:

       + ------------------------------------------------- -------------- ký bit
      / + ------- + ---------------------------------------- -------------- số mũ
     / / | + ------------------------------------------------- + - sigand
    / / | / |
    0 01111111111 0000000000000000000000000000000000000000000000000000
                    = 1
    0 01111111111 0000000000000000000000000000000000000000000000000001
                    ≈ 1,00000000000000022
    0 01111111111 0000000000000000000000000000000000000000000000000010
                    ≈ 1,00000000000000044
    

    Lưu ý bước nhảy từ 1,00000000000000022 đến 1,00000000000000044; không có cách nào để lưu trữ 1.0000000000000003. Điều đó cũng có thể xảy ra với số nguyên: Number.MAX_SAFE_INTEGER(9,007,199,254,740,991) là giá trị số nguyên dương cao nhất mà định dạng có thể giữ ở vị trí ii + 1cả hai đều có thể biểu diễn chính xác ( spec ). Cả 9,007,199,254,740,991 và 9,007,199,254,740,992 đều có thể được biểu diễn, nhưng số nguyên tiếp theo , 9,007,199,254,740,993, không thể; số nguyên tiếp theo mà chúng ta có thể biểu diễn sau 9,007,199,254,740,992 là 9,007,199,254,740,994. Dưới đây là các mẫu bit, lưu ý bit ngoài cùng bên phải (ít quan trọng nhất):

       + ------------------------------------------------- -------------- ký bit
      / + ------- + ---------------------------------------- -------------- số mũ
     / / | + ------------------------------------------------- + - sigand
    / / | / |
    0 10000110011 1111111111111111111111111111111111111111111111111111
                    = 9007199254740991 (Số.MAX_SAFE_INTEGER)
    0 10000110100 0000000000000000000000000000000000000000000000000000
                    = 9007199254740992 (Số.MAX_SAFE_INTEGER + 1)
    x xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                      9007199254740993 (Number.MAX_SAFE_INTEGER + 2) không thể được lưu trữ
    0 10000110100 0000000000000000000000000000000000000000000000000001
                    = 9007199254740994 (Số.MAX_SAFE_INTEGER + 3)
    

    Hãy nhớ rằng, định dạng là cơ số 2 và với số mũ đó, bit có ý nghĩa nhỏ nhất không còn là phân số; nó có giá trị là 2. Nó có thể tắt (9,007,199,254,740,992) hoặc trên (9,007,199,254,740,994); vì vậy tại thời điểm này, chúng ta đã bắt đầu mất độ chính xác ngay cả ở thang số nguyên (số nguyên). Điều đó có ý nghĩa đối với vòng lặp của chúng tôi!

  4. Sau khi hoàn thành i = 9,007,199,254,740,992vòng lặp, i++cho chúng ta ... i = 9,007,199,254,740,992một lần nữa; không có thay đổi nào i, vì không thể lưu trữ số nguyên tiếp theo và kết thúc phép tính làm tròn số. isẽ thay đổi nếu chúng tôi đã làm i += 2, nhưng i++không thể thay đổi nó. Vì vậy, chúng tôi đã đạt đến trạng thái ổn định: ikhông bao giờ thay đổi và vòng lặp không bao giờ kết thúc.

Dưới đây là các tính toán liên quan khác nhau:

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

Câu trả lời:

Điều kiện 1/i > 0sẽ luôn đánh giá là true:

  • Ban đầu nó đúng vì 1/0đánh giá InfinityInfinity > 0đúng

  • Nó vẫn đúng vì 1/i > 0nó đúng cho tất cả i < Infinityi++không bao giờ đạt tới Infinity.

Tại sao không i++bao giờ đạt được Infinity? Do độ chính xác hạn chế của Numberkiểu dữ liệu, có một giá trị cho i + 1 == i:

9007199254740992 + 1 == 9007199254740992 // true

Khi iđạt đến giá trị đó (tương ứng với ), nó sẽ giữ nguyên ngay cả sau đó .Number.MAX_SAFE_INTEGER + 1i++

Do đó, chúng ta có một vòng lặp vô hạn.


Ruột thừa:

Tại sao là 9007199254740992 + 1 == 9007199254740992?

NumberKiểu dữ liệu của JavaScript thực sự là một float chính xác kép 64-bit IEEE 754 . Mỗi phần Numberđược tháo rời và lưu trữ thành ba phần: dấu 1 bit, số mũ 11 bit và phần định trị 52 bit. Giá trị của nó là -1 dấu × phần định trị × 2 số mũ .

Làm thế nào là 9007199254740992 đại diện? Dưới dạng 1,0 × 2 53 hoặc ở dạng nhị phân:

nhập mô tả hình ảnh ở đây

Tăng bit ít quan trọng nhất của phần định trị, chúng tôi nhận được số cao hơn tiếp theo:

nhập mô tả hình ảnh ở đây

Giá trị của số đó là 1,00000000000000022… × 2 53 = 9007199254740994

Điều đó nghĩa là gì? Numbercó thể là 900719925474099 2 hoặc 900719925474099 4 , nhưng không có gì ở giữa.

Bây giờ, chúng ta sẽ chọn cái nào để đại diện cho 900719925474099 2 + 1 ? Các quy tắc làm tròn 754 của IEEE đưa ra câu trả lời: 900719925474099 2 .


9
ngắn gọn và chính xác, tốt hơn câu trả lời được chấp nhận hiện tại
AlexWien

@AlexWien Câu trả lời được chấp nhận là câu trả lời được cộng đồng wiki chấp nhận.
fulvio

2
Tôi không biết câu trả lời của thuật ngữ "cộng đồng wiki đã accpised". Điều này có liên quan gì đến stackoverflow? Nếu đây là một liên kết nước ngoài, một liên kết nên được cung cấp. Các câu trả lời được chấp nhận trên stackoverflow luôn có thể thay đổi, trạng thái được chấp nhận không phải là cuối cùng.
AlexWien

"Tại sao i ++ không bao giờ đạt đến Vô cực? Do độ chính xác có hạn của kiểu dữ liệu Số ..." <- Chắc chắn nó sẽ không bao giờ đạt đến vô cùng, ngay cả với loại số chính xác vô hạn .. Bạn biết đấy, vì bạn không thể đếm đến infinity: P
Blorgbeard ra mắt vào

1
@Blorgbeard Bạn có thể đếm đến Infinity với đôi chính xác hạn chế, bạn chỉ cần tăng bởi một số lớn hơn nhiều so với 1, ví dụ for (var i = 0; i < Infinity; i += 1E306);. Nhưng tôi biết bạn đến từ đâu;)
le_m

27

Các Number.MAX_SAFE_INTEGERhằng số đại diện cho số nguyên an toàn tối đa trong JavaScript. Các MAX_SAFE_INTEGERhằng số có giá trị 9007199254740991. Lý do đằng sau con số đó là JavaScript sử dụng số định dạng dấu phẩy động có độ chính xác kép như được chỉ định trong IEEE 754 và chỉ có thể biểu diễn một cách an toàn các số từ - (2 53 - 1) đến 2 53 - 1.

An toàn trong ngữ cảnh này đề cập đến khả năng biểu diễn chính xác các số nguyên và so sánh chúng một cách chính xác. Ví dụ, Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2sẽ đánh giá thành true, không chính xác về mặt toán học. Xem Number.isSafeInteger()để biết thêm thông tin.

MAX_SAFE_INTEGERlà thuộc tính tĩnh của Numbernên bạn luôn sử dụng nó Number.MAX_SAFE_INTEGERnhư một thuộc tính thay vì là thuộc tính của một Numberđối tượng bạn đã tạo.

CẬP NHẬT:

Ai đó trong một câu trả lời đã bị xóa đã đề cập đến: isẽ không bao giờ đạt đến vô cùng. Khi nó đạt đến Number.MAX_SAFE_INTEGER, i++không tăng biến nữa. Điều này trong thực tế là không chính xác.

@TJ Crowder nhận xét đó i = Number.MAX_SAFE_INTEGER; i++; i == Number.MAX_SAFE_INTEGER;false. Nhưng lần lặp tiếp theo đạt đến trạng thái không thay đổi, vì vậy câu trả lời trong main là đúng.

itrong ví dụ không bao giờ đạt Infinity.


2
Cụ thể, 9007199254740992 + 19007199254740992.
Kobi

1
@GerardoFurtado Tôi sẽ tưởng tượng nó sẽ như vậy.
fulvio

1
@GerardoFurtado for (var i=0; NaN > 0; i++) { console.log(i); }sẽ không sản xuất bất cứ thứ gì.
fulvio

2
@GerardoFurtado: Trong trường hợp đó, vòng lặp sẽ dừng lại. Phần thân của vòng lặp sẽ không bao giờ được nhập, vì test đầu tiên ( 1/i > 0) sẽ là false, vì if i0, 1/iis NaNNaN > 0là false.
TJ Crowder

1
@TJCrowder Tôi đã cập nhật câu trả lời của mình. Cảm ơn vì đã chỉ ra điều đó!
fulvio
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.