ค่าอะไรของฉันในขณะที่ (i == i + 1) {} วนซ้ำตลอดไป


120

ฉันวิ่งข้ามปริศนานี้จากหลักสูตรการเขียนโปรแกรมขั้นสูงในการสอบของมหาวิทยาลัยในสหราชอาณาจักร

พิจารณาลูปต่อไปนี้ซึ่งฉันอยู่จนถึงตอนนี้ยังไม่ได้ประกาศ:

while (i == i + 1) {}

ค้นหาคำจำกัดความของiที่นำหน้าลูปนี้เพื่อให้ลูป while ดำเนินต่อไปตลอดกาล

คำถามถัดไปซึ่งถามคำถามเดียวกันสำหรับข้อมูลโค้ดนี้:

while (i != i) {}

เห็นได้ชัดสำหรับฉัน แน่นอนในสถานการณ์อื่น ๆ นี้NaNแต่ฉันติดอยู่กับเหตุการณ์ก่อนหน้านี้จริงๆ สิ่งนี้เกี่ยวข้องกับการล้นหรือไม่? อะไรจะทำให้ลูปดังกล่าววนซ้ำไปตลอดกาลใน Java


3
มีความเป็นไปได้ที่จะลบล้าง.equals()วิธีการหรือไม่? เนื่องจากฉันไม่ได้ประกาศเราอาจใช้คลาสใดก็ได้ตามที่เราต้องการ
Geno Chen

7
@Raedwald การเรียนโค้ดแบบ "ไม่เป็นมืออาชีพ" ทำให้คุณเป็น "มืออาชีพ" มากขึ้นดังนั้น ... ยังไงก็ตามมันเป็นคำถามที่ดี
Andrew Tobilko

12
ความเป็นจริงความสนุกสนานใน C # นี้ยังสามารถใช้ได้กับชนิดของตัวเลข nullable ที่มีค่าเป็นnullตั้งแต่null == nullเป็นความจริงและเป็นnull + 1 null
Eric Lippert

4
@EricDuminil: สถานการณ์เลวร้ายกว่าที่คุณคิด ในหลายภาษาลอยคำนวณจุดจะต้องทำในอย่างน้อย 64 บิตของความแม่นยำระบุโดยคู่ซึ่งหมายความว่ามันสามารถทำได้ในความแม่นยำที่สูงขึ้นที่ราชประสงค์ของคอมไพเลอร์, และในทางปฏิบัตินี้เกิดขึ้น ฉันสามารถชี้ให้คุณเห็นคำถามหลายสิบข้อในไซต์นี้จากโปรแกรมเมอร์ C # ที่สงสัยว่าทำไมถึง0.2 + 0.1 == 0.3เปลี่ยนค่าของมันขึ้นอยู่กับการตั้งค่าคอมไพเลอร์ระยะของดวงจันทร์และอื่น ๆ
Eric Lippert

5
@EricDuminil: ข้อตำหนิสำหรับความยุ่งเหยิงนี้ตกอยู่กับ Intel ซึ่งให้ชิปเซ็ตแก่เราที่ทำเลขคณิตทศนิยมที่มีความแม่นยำสูงขึ้นและเร็วขึ้นหากสามารถลงทะเบียนตัวเลขได้ซึ่งหมายความว่าผลลัพธ์ของการคำนวณจุดลอยตัวสามารถเปลี่ยนค่าได้ขึ้นอยู่กับ ว่าตัวกำหนดตารางเวลาการลงทะเบียนในเครื่องมือเพิ่มประสิทธิภาพทำงานได้ดีเพียงใดในปัจจุบัน ตัวเลือกของคุณในฐานะนักออกแบบภาษาจะอยู่ระหว่างการคำนวณที่ทำซ้ำได้และการคำนวณที่รวดเร็วและแม่นยำและชุมชนที่ให้ความสำคัญกับคณิตศาสตร์แบบทศนิยมจะเลือกอย่างหลัง
Eric Lippert

คำตอบ:


142

ก่อนอื่นเนื่องจากwhile (i == i + 1) {}ลูปไม่ได้เปลี่ยนค่าของiการทำให้ลูปนี้ไม่มีที่สิ้นสุดจึงเท่ากับการเลือกค่าของiความพึงพอใจi == i + 1นั้น

มีหลายค่าดังกล่าว:

เริ่มกันที่ "แปลกใหม่":

double i = Double.POSITIVE_INFINITY;

หรือ

double i =  Double.NEGATIVE_INFINITY;

เหตุผลที่ทำให้ค่าเหล่านี้i == i + 1เป็นที่พึงพอใจระบุไว้ใน
JLS 15.18.2 ตัวดำเนินการเพิ่มเติม (+ และ -) สำหรับประเภทตัวเลข :

ผลรวมของอินฟินิตี้และค่า จำกัด เท่ากับค่าไม่สิ้นสุดตัวถูกดำเนินการ

สิ่งนี้ไม่น่าแปลกใจเนื่องจากการเพิ่มค่า จำกัด ให้กับค่าอนันต์ควรทำให้ได้ค่าที่ไม่สิ้นสุด

ที่กล่าวว่าค่าส่วนใหญ่ของiความพึงพอใจi == i + 1นั้นเป็นค่าขนาดใหญ่double(หรือfloat):

ตัวอย่างเช่น:

double i = Double.MAX_VALUE;

หรือ

double i = 1000000000000000000.0;

หรือ

float i = 1000000000000000000.0f;

doubleและfloatประเภทมีความแม่นยำ จำกัด ดังนั้นถ้าคุณใช้เวลามากพอdoubleหรือfloatมูลค่าเพิ่ม1ให้กับมันจะส่งผลให้ค่าเดียวกัน


10
หรือ(double)(1L<<53)- หรือfloat i = (float)(1<<24)
dave_thompson_085

3
@Ruslan: นักคณิตศาสตร์ทุกคนไม่เห็นด้วย ตัวเลขจุดลอยมีความหมายน้อยมาก พวกมันไม่เชื่อมโยงไม่สะท้อนกลับ (NaN! = NaN) และไม่สามารถทดแทนได้ (-0 == 0 แต่ 1/0! = 1 / -0) ดังนั้นกลไกส่วนใหญ่ของพีชคณิตจึงใช้ไม่ได้
Kevin

2
@ เควินในขณะที่ตัวเลขทศนิยมไม่สามารถมีความหมายได้มากนักโดยทั่วไปพฤติกรรมของ infinities (ซึ่งเป็นสิ่งที่อธิบายไว้ในประโยคนั้น) ได้รับการออกแบบมาเพื่อให้สมเหตุสมผล
Ruslan

4
@Kevin เพื่อความยุติธรรมในการลอยตัวหากคุณจัดการกับ infinities หรือค่าที่ไม่ได้กำหนดคุณจะไม่สามารถถือว่าคุณสมบัติที่คุณระบุไว้ในพีชคณิตได้เช่นกัน
Voo

2
@ เควิน: IMHO คณิตศาสตร์ทศนิยมอาจมีความหมายมากกว่านี้หากพวกเขาแทนที่แนวคิดของเครื่องหมาย "ศูนย์บวกและลบ" ในเชิงบวกลบและไม่ได้ลงนามพร้อมกับ "ศูนย์จริง" หนึ่งตัวและทำให้ NaN เท่ากับตัวมันเอง ศูนย์ที่แท้จริงสามารถทำงานเป็นข้อมูลประจำตัวเสริมในทุกกรณีและการดำเนินการบางอย่างที่เกี่ยวข้องกับการแบ่งโดยสัตว์เล็ก ๆ น้อย ๆ จะสูญเสียความเอนเอียงไปสู่การสมมติว่าสัตว์เล็กน้อยเป็นค่าบวก
supercat

64

ปริศนาเหล่านี้มีคำอธิบายโดยละเอียดในหนังสือ "Java Puzzlers: Traps, Pitfalls และ Corner Cases" โดย Joshua Bloch และ Neal Gafter

double i = Double.POSITIVE_INFINITY;
while (i == i + 1) {}

หรือ:

double i = 1.0e40;
while (i == i + 1) {}

ทั้งสองจะส่งผลในวง จำกัด เพราะการเพิ่ม1ให้เป็นค่าที่จุดลอยตัวที่มีขนาดใหญ่พอที่จะไม่เปลี่ยนค่าเพราะมันไม่ได้ "ลดช่องว่าง" เพื่อสืบ1

หมายเหตุเกี่ยวกับปริศนาที่สอง (สำหรับผู้อ่านในอนาคต):

double i = Double.NaN;
while (i != i) {}

นอกจากนี้ยังส่งผลในวง จำกัด เพราะน่านไม่เท่ากับค่าจุดลอยตัวใด ๆ รวมทั้งตัวเอง 2


1 - Java Puzzlers: กับดักหลุมพรางและกรณีมุม (บทที่ 4 - Loopy Puzzlers)

2 - JLS §15.21.1



0

แค่ความคิด: แล้วบูลีนล่ะ?

bool i = TRUE;

นี่ไม่ใช่กรณีที่i + 1 == i?


ขึ้นอยู่กับภาษา หลายภาษาจะบังคับบูลีนให้เป็น int โดยอัตโนมัติเมื่อรวมกับ int คนอื่น ๆ ทำตามที่คุณแนะนำ - บังคับ int ให้เป็นบูลีน
Carl Witthoft

8
คำถามนี้เป็นคำถาม Java และคำแนะนำของคุณไม่ผ่านการคอมไพล์ใน Java (ซึ่งไม่มี+ตัวดำเนินการที่ใช้ a booleanและintเป็นตัวถูกดำเนินการ)
เอราน

@Eran: นั่นคือความคิดทั้งหมดของตัวดำเนินการที่มากเกินไป คุณสามารถทำให้บูลีนของ Java ทำงานเหมือน C ++ ได้
Dominique

4
ยกเว้นว่า Java ไม่รองรับตัวดำเนินการที่โอเวอร์โหลดดังนั้นคุณจึงไม่สามารถทำได้
CupawnTae
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.