การวนซ้ำ 'for' นี้หยุดหรือไม่และทำไม / ทำไมไม่? สำหรับ (var i = 0; 1 / i> 0; i ++) {}


104

forลูปนี้ไม่เคยหยุด?

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

ถ้าเป็นเช่นนั้นเมื่อไรและทำไม? ฉันบอกว่ามันหยุด แต่ฉันไม่ได้รับเหตุผลสำหรับสิ่งนั้น

Upddate

ในการสืบสวนฉันได้เขียนบทความที่ค่อนข้างยาวและมีรายละเอียดซึ่งจะอธิบายทุกสิ่งที่เกิดขึ้นภายใต้ประทุน - นี่คือสิ่งที่คุณต้องรู้เกี่ยวกับประเภท Number ของ JavaScript


5
มันจะไม่หยุด ลองเรียกใช้โค้ดชิ้นนี้ สำหรับ (var i = 0; 1 / i> 0; i ++) {console.log (i)}
Sourabh Agrawal

3
ที่เกี่ยวข้อง: เวลาคำนวณนำตัวไปทำลายที่สำคัญ AESจากความปลอดภัยของข้อมูลเดิมโดยSchneier ในการรักษาความปลอดภัย
Kobi

3
Number.MAX_VALUE + 9.979202e291 == "Infinity" และ 1 / (NaN หรือ "Infinity" หรือ "undefined")> 0 == false
askeet

6
Javascript จะเพิกเฉยต่อการวนซ้ำนั้นเนื่องจากไม่มีข้อความภายในหรือไม่ เช่น Optimize ไปหรือไม่ ฉันรู้ว่ามีภาษาคอมไพล์บางภาษาที่จะ
Brian J

3
@askeet เป็นgotnullและคนอื่น ๆ ชี้ให้เห็นด้านล่างเราไม่เคยไปถึงอินฟินิตี้โดยการเพิ่มซ้ำ ๆ 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/iget จะน้อยมากในตอนท้าย!) ดังนั้นการวนซ้ำจะดำเนินต่อไปจนถึงและรวมถึงลูปที่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 00000000000000000000000000000000000000000000000000000000
                    = 1
    0 01111111111 00000000000000000000000000000000000000000000000000000001
                    ≈ 1.00000000000000022
    0 01111111111 00000000000000000000000000000000000000000000000000000010
                    ≈ 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 11111111111111111111111111111111111111111111111111
                    = 9007199254740991 (Number.MAX_SAFE_INTEGER)
    0 10000110100 00000000000000000000000000000000000000000000000000000000
                    = 9007199254740992 (Number.MAX_SAFE_INTEGER + 1)
    x xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                      9007199254740993 (Number.MAX_SAFE_INTEGER + 2) ไม่สามารถจัดเก็บได้
    0 10000110100 00000000000000000000000000000000000000000000000000000001
                    = 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,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?

JavaScript ของNumberประเภทข้อมูลเป็นจริง 64 บิตIEEE 754 ลอยแม่นยำสอง แต่ละNumberชิ้นจะถูกถอดประกอบและจัดเก็บเป็นสามส่วน: เครื่องหมาย 1 บิตเลขชี้กำลัง 11 บิตและแมนทิสซา 52 บิต ค่าที่เป็น -1 เครื่องหมาย × mantissa × 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 คำตอบที่ยอมรับคือคำตอบที่ได้รับการยอมรับจากชุมชน wiki
fulvio

2
ฉันไม่ทราบคำตอบของคำว่า "community wiki accpeted" สิ่งนี้เกี่ยวข้องกับ Stackoverflow อย่างไร หากเป็นลิงค์ต่างประเทศควรมีลิงค์ คำตอบที่ยอมรับใน stackoverflow สามารถเปลี่ยนแปลงได้เสมอสถานะที่ยอมรับจะไม่เป็นที่สิ้นสุด
AlexWien

"เหตุใด i ++ จึงไม่ไปถึง Infinity เนื่องจากความแม่นยำที่ จำกัด ของประเภทข้อมูล 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แต่การวนซ้ำครั้งถัดไปจะถึงสถานะที่ไม่เปลี่ยนแปลงดังนั้นคำตอบใน main จึงถูกต้อง

iInfinityในตัวอย่างไม่ถึง


2
โดยเฉพาะอย่างยิ่งคือ9007199254740992 + 1 9007199254740992
Kobi

1
@GerardoFurtado ฉันคิดว่ามันจะ
fulvio

1
@GerardoFurtado for (var i=0; NaN > 0; i++) { console.log(i); }จะไม่ผลิตอะไรเลย
fulvio

2
@GerardoFurtado: ในกรณีนั้นการวนซ้ำจะหยุดลง ร่างกายห่วงจะไม่ได้เข้ามาเลยตั้งแต่การทดสอบครั้งแรกมาก ( 1/i > 0) จะเป็นเท็จเพราะถ้าiเป็น0, 1/iเป็นNaNและNaN > 0เป็นเท็จ
TJ Crowder

1
@TJCrowder ฉันอัปเดตคำตอบของฉันแล้ว ขอบคุณที่ชี้ให้ดู!
fulvio
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.