Bu 'for' döngüsü durur mu ve neden / neden olmasın? for (var i = 0; 1 / i> 0; i ++) {}


104

Bu fordöngü hiç durur mu?

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

Öyleyse, ne zaman ve neden? Bana durduğu söylendi, ama bunun için hiçbir neden verilmedi.

Güncelle

Araştırmanın bir parçası olarak, başlık altında neler olup bittiğini açıklayan oldukça uzun ve ayrıntılı bir makale yazdım - İşte JavaScript'in Sayı türü hakkında bilmeniz gerekenler


5
Durmayacak. bu kod parçasını çalıştırmayı deneyin. için (var i = 0; 1 / i> 0; i ++) {console.log (i)}
Sourabh Agrawal


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

6
Javascript, içinde ifade bulunmadığı için bu döngüyü yok sayacak mı? yani Optimize etsin mi? Bunu yapacak bazı derlenmiş diller olduğunu biliyorum.
Brian J

3
@askeet, gotnull ve diğerlerinin de belirttiği gibi, Infinity'ye asla tekrar tekrar artarak ulaşmıyoruz, bunun yerine bir döngüye giriyoruz Number.MAX_SAFE_INTEGER + 1.
LSpice

Yanıtlar:


128

(Meta içerik hayranı değilim, ancak: gotnull'ın ve le_m'in yanıtları hem doğru hem de kullanışlıdır. Orijinallerdi ve bu Topluluk Wiki yayınlandıktan sonra yapılan düzenlemelerle daha da fazlası . Bu CW için orijinal motivasyon bu düzenlemelerin bir sonucu olarak büyük ölçüde ortadan kalktı, ancak yararlı olmaya devam ediyor, bu yüzden ... Ayrıca: Listelenen yalnızca birkaç yazar varken, diğer birçok topluluk üyesi katlanmış ve temizlenmiş yorumlarda büyük ölçüde yardımcı oldu. adı sadece bir CW değildir.)


Döngü, doğru şekilde uygulanan bir JavaScript motorunda durmaz. (Motorun ana bilgisayar ortamı, sonsuz olduğu için sonunda onu sonlandırabilir, ancak bu başka bir şey.)

İşte nedeni:

  1. Başlangıçta, ne zaman i, 0koşul 1/i > 0doğrudur çünkü JavaScript'te, 1/0eşittir Infinityve Infinity > 0doğrudur.

  2. Bundan sonra, iartırılacak ve uzun süre pozitif bir tamsayı değeri olarak büyümeye devam edecektir (daha fazla 9,007,199,254,740,991 yineleme). Bu gibi durumlarda tümünde, 1/ikalacak > 0(her ne kadar değerleri 1/ialmak gerçekten sonuna doğru küçük!) Döngü döngü ve dahil kadar devam eder ve böylece ibir değere ulaşır Number.MAX_SAFE_INTEGER.

  3. JavaScript'teki sayılar IEEE-754 çift duyarlıklı ikili kayan nokta, hızlı hesaplamalar ve geniş bir aralık sağlayan oldukça kompakt bir biçimdir (64 bit). Bunu, sayıyı bir işaret biti, 11 bitlik bir üs ve 52 bitlik bir anlamlılık olarak saklayarak yapar (zekâ sayesinde aslında 53 bitlik kesinlik elde etmesine rağmen). Bu var ikili significand (artı bazı zeka) bize değer verir ve üs bize sayısının büyüklüğünü verir: (baz 2) kayan nokta.

    Doğal olarak, sadece bu kadar çok önemli bit ile her sayı saklanamaz. Burada 1 sayısı ve formatın saklayabileceği 1'den sonraki en yüksek sayı, 1 + 2 -52 ≈ 1.00000000000000022 ve bundan sonraki en yüksek 1 + 2 × 2 -52 ≈ 1.00000000000000044:

       + ------------------------------------------------- -------------- işaret biti
      / + ------- + ---------------------------------------- -------------- üs
     / / | + ------------------------------------------------- + - anlamlı
    / / | / |
    0 01111111111 0000000000000000000000000000000000000000000000000000
                    = 1
    0 01111111111 000000000000000000000000000000000000000000000000000001
                    ≈ 1.00000000000000022
    0 01111111111 0000000000000000000000000000000000000000000000000010
                    ≈ 1.00000000000000044
    

    1.00000000000000022'den 1.00000000000000044'e atlamayı unutmayın; 1.0000000000000003'ü saklamanın bir yolu yoktur. Bu, tamsayılarla da olabilir: Number.MAX_SAFE_INTEGER(9,007,199,254,740,991), formatın tutabileceği ive i + 1her ikisi de tam olarak gösterilebilir ( spec ) olan en yüksek pozitif tam sayı değeridir . Hem 9,007,199,254,740,991 hem de 9,007,199,254,740,992 temsil edilebilir, ancak sonraki tam sayı olan 9,007,199,254,740,993 gösterilemez; 9.007.199.254.740.992'den sonra temsil edebileceğimiz bir sonraki tam sayı 9.007.199.254.740.994'tür. İşte bit kalıpları, en sağdaki (en az önemli) biti not edin:

       + ------------------------------------------------- -------------- işaret biti
      / + ------- + ---------------------------------------- -------------- üs
     / / | + ------------------------------------------------- + - anlamlı
    / / | / |
    0 10000110011 1111111111111111111111111111111111111111111111111111
                    = 9007199254740991 (Sayı.MAX_SAFE_INTEGER)
    0 10000110100 0000000000000000000000000000000000000000000000000000
                    = 9007199254740992 (Sayı.MAX_SAFE_INTEGER + 1)
    x xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                      9007199254740993 (Number.MAX_SAFE_INTEGER + 2) depolanamaz
    0 10000110100 000000000000000000000000000000000000000000000000000001
                    = 9007199254740994 (Sayı.MAX_SAFE_INTEGER + 3)
    

    Unutmayın, format 2 tabanlıdır ve bu üs ile en önemsiz bit artık kesirli değildir; 2 değerine sahiptir. Kapalı (9,007,199,254,740,992) veya açık (9,007,199,254,740,994) olabilir; yani bu noktada, tam sayı (tamsayı) ölçeğinde bile hassasiyeti kaybetmeye başladık. Döngümüz için etkileri var!

  4. i = 9,007,199,254,740,992Döngüyü tamamladıktan sonra i++bize i = 9,007,199,254,740,992tekrar verir ; herhangi bir değişiklik yoktur i, çünkü bir sonraki tamsayı saklanamaz ve hesaplama aşağı yuvarlanır. iyapsaydık değişirdi i += 2, ama i++değiştiremeyiz. Böylece kararlı duruma ulaştık: iasla değişmez ve döngü asla sona ermez.

İşte çeşitli ilgili hesaplamalar:

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

Cevap:

Koşul 1/i > 0her zaman doğru olarak değerlendirilir:

  • Başlangıçta doğrudur çünkü 1/0değerlendirilir Infinityve Infinity > 0doğrudur

  • Doğrudur çünkü 1/i > 0herkes için geçerlidir i < Infinityve i++asla ulaşmaz Infinity.

Neden i++asla ulaşmıyor Infinity? NumberVeri türünün sınırlı kesinliği nedeniyle, aşağıdakileri sağlayan bir değer vardır i + 1 == i:

9007199254740992 + 1 == 9007199254740992 // true

Bir kez ideğeri (karşılık gelen bu ulaşır ), bu da sonra, aynı kalacaktır .Number.MAX_SAFE_INTEGER + 1i++

Bu nedenle sonsuz bir döngümüz var.


Ek:

Neden 9007199254740992 + 1 == 9007199254740992?

JavaScript'in Numberveri türü aslında 64 bitlik bir IEEE 754 çift duyarlıklı kayan nokta . Her Numberbiri demonte edilir ve üç parça olarak saklanır: 1 bitlik işaret, 11 bitlik üs ve 52 bitlik mantis. Değeri -1 işareti × mantis × 2 üssüdür .

9007199254740992 nasıl temsil edilir? Olarak 1.0 x 2 53 ya da ikili olarak:

görüntü açıklamasını buraya girin

Mantisin en önemsiz bitini artırarak, bir sonraki daha yüksek sayıyı elde ederiz:

görüntü açıklamasını buraya girin

Bu sayının değeri 1.00000000000000022… × 2 53 = 9007199254740994

Bu ne anlama geliyor? Number900719925474099 2 veya 900719925474099 4 olabilir , ancak ikisi arasında hiçbir şey yoktur.

Şimdi 900719925474099 2 + 1'i temsil etmeyi hangisini seçeceğiz ? IEEE 754 yuvarlama kuralları 900719925474099: cevap vermek 2 .


9
kısa ve doğru, şu anki kabul edilen cevaptan daha iyi
AlexWien

@AlexWien Kabul edilen yanıt, topluluk wiki tarafından kabul edilen bir yanıttır.
fulvio

2
"Topluluk wiki onaylandı" teriminin cevabını bilmiyorum. Bunun stackoverflow ile ne ilgisi var? Bu yabancı bir bağlantı ise, bir bağlantı sağlanmalıdır. Stackoverflow'da kabul edilen yanıtlar her zaman değişebilir, kabul edilen durum nihai değildir.
AlexWien

"İ ++ neden Sonsuza ulaşmıyor? Sayı veri türünün sınırlı kesinliği nedeniyle ..." <- Kesinlikle sonsuz kesinlikli bir sayı türünde bile sonsuza ulaşmaz .. Biliyorsunuz, çünkü sonsuzluk: P
Blorgbeard

1
@Blorgbeard Sınırlı hassasiyet çiftleri ile Infinity'ye kadar sayabilirsiniz, sadece 1'den çok daha büyük bir sayı artırmanız gerekir, örn for (var i = 0; i < Infinity; i += 1E306);. Ama nereden geldiğinizi anlıyorum;)
le_m

27

Number.MAX_SAFE_INTEGERSabiti JavaScript maksimum güvenli tamsayı temsil eder. MAX_SAFE_INTEGERSabit bir değere sahiptir 9007199254740991. Bu sayının arkasındaki mantık, JavaScript'in IEEE 754'te belirtildiği gibi çift ​​duyarlıklı kayan noktalı biçim sayıları kullanması ve yalnızca - (2 53-1 ) ile 2 53-1 arasındaki sayıları güvenli bir şekilde temsil edebilmesidir .

Bu bağlamda güvenli, tam sayıları tam olarak temsil etme ve bunları doğru şekilde karşılaştırma yeteneğini ifade eder. Örneğin , matematiksel olarak yanlış olan olarak Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2değerlendirilecektir true. Daha Number.isSafeInteger()fazla bilgi için bakın .

Öğesinin MAX_SAFE_INTEGERstatik bir özelliği olduğu için Number, onu oluşturduğunuz Number.MAX_SAFE_INTEGERbir Numbernesnenin özelliği yerine her zaman olarak kullanırsınız .

GÜNCELLEME:

Belirtilen silinmiş bir cevapta biri: iasla sonsuzluğa ulaşmayacak. Bir kez ulaştığında Number.MAX_SAFE_INTEGER, i++artık değişkeni artırmaz. Bu aslında doğru değil .

@TJ Crowder yorumunu yapmaktadır i = Number.MAX_SAFE_INTEGER; i++; i == Number.MAX_SAFE_INTEGER;olduğunu false. Ancak bir sonraki yineleme değişmeyen bir duruma ulaşır, bu nedenle temelde cevap doğrudur.

iörnekte asla ulaşmaz Infinity.


2
Özellikle, 9007199254740992 + 1öyle 9007199254740992.
Kobi

1
@GerardoFurtado olacağını düşünürdüm.
fulvio

1
@GerardoFurtado for (var i=0; NaN > 0; i++) { console.log(i); }hiçbir şey üretmeyecek.
fulvio

2
@GerardoFurtado: Bu durumda döngü durur. Döngü gövdesi ilk testi (çünkü hiç girilmesi asla 1/i > 0) eğer bu yana, yanlış olur iise 0, 1/iolduğu NaNve NaN > 0yanlıştır.
TJ Crowder

1
@TJCrowder Cevabımı güncelledim. Bunu belirttiğiniz için teşekkürler!
fulvio
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.