Với giá trị nào của i thì lặp while (i == i + 1) {} mãi mãi?


120

Tôi đã vượt qua câu đố này từ một khóa học lập trình nâng cao tại một kỳ thi đại học ở Vương quốc Anh .

Hãy xem xét vòng lặp sau, trong đó tôi, cho đến nay, chưa được khai báo:

while (i == i + 1) {}

Tìm định nghĩa của i, đứng trước vòng lặp này, sao cho vòng lặp while tiếp tục mãi mãi.

Câu hỏi tiếp theo, đặt câu hỏi tương tự cho đoạn mã này:

while (i != i) {}

là điều hiển nhiên đối với tôi. Tất nhiên là trong tình huống này, NaNnhưng tôi thực sự bị mắc kẹt với cái trước. Điều này có phải làm với tràn? Điều gì sẽ khiến một vòng lặp như vậy lặp đi lặp lại mãi mãi trong Java?


3
Bất kỳ khả năng nào để ghi đè .equals()phương thức? Vì tôi không được khai báo, chúng tôi có thể sử dụng bất kỳ lớp nào của những gì chúng tôi muốn.
Geno Chen

7
@Raedwald nghiên cứu "không chuyên nghiệp" mã làm cho bạn nhiều hơn "chuyên nghiệp", vì vậy ... Dù sao, đó là một câu hỏi hay
Andrew Tobilko

12
Thực tế thú vị là trong C #, điều này cũng hoạt động đối với các kiểu số có thể rỗng có giá trị null, vì null == nulllà true và null + 1null.
Eric Lippert

4
@EricDuminil: Tình hình tồi tệ hơn nhiều so với những gì bạn tưởng tượng. Trong nhiều ngôn ngữ, số học dấu phẩy động phải được thực hiện với độ chính xác ít nhất là 64 bit được chỉ định bởi một kép, có nghĩa là nó có thể được thực hiện với độ chính xác cao hơn theo ý thích của trình biên dịch và trong thực tế, điều này xảy ra . Tôi có thể chỉ cho bạn hàng tá câu hỏi trên trang web này từ các lập trình viên C #, những người đang tự hỏi tại sao lại 0.2 + 0.1 == 0.3thay đổi giá trị của nó tùy thuộc vào cài đặt trình biên dịch, giai đoạn của mặt trăng, v.v.
Eric Lippert

5
@EricDuminil: Nguyên nhân cho sự lộn xộn này thuộc về Intel, người đã cung cấp cho chúng tôi một bộ chip thực hiện số học dấu phẩy động có độ chính xác cao hơnnhanh hơn nếu các số có thể được đăng ký, có nghĩa là kết quả của phép tính dấu phẩy động có thể thay đổi giá trị của chúng tùy thuộc về mức độ hoạt động của bộ lập lịch đăng ký trong trình tối ưu hóa hiện nay. Khi đó, lựa chọn của bạn với tư cách là nhà thiết kế ngôn ngữ là giữa tính toán có thể lặp lạitính toán nhanh, chính xác và cộng đồng quan tâm đến toán học dấu phẩy động sẽ chọn tính toán thứ hai.
Eric Lippert

Câu trả lời:


142

Trước hết, vì while (i == i + 1) {}vòng lặp không thay đổi giá trị của i, việc làm cho vòng lặp này trở nên vô hạn tương đương với việc chọn một giá trị ithỏa mãn i == i + 1.

Có nhiều giá trị như vậy:

Hãy bắt đầu với những cái "kỳ lạ":

double i = Double.POSITIVE_INFINITY;

hoặc là

double i =  Double.NEGATIVE_INFINITY;

Lý do thỏa mãn các giá trị i == i + 1này được nêu trong
JLS 15.18.2. Toán tử cộng (+ và -) cho kiểu số :

Tổng của một vô cùng và một giá trị hữu hạn bằng toán hạng vô hạn.

Điều này không có gì đáng ngạc nhiên, vì thêm một giá trị hữu hạn vào một giá trị vô hạn sẽ dẫn đến một giá trị vô hạn.

Điều đó nói rằng, hầu hết các giá trị của ithỏa mãn i == i + 1đó chỉ đơn giản là giá trị lớn double(hoặc float):

Ví dụ:

double i = Double.MAX_VALUE;

hoặc là

double i = 1000000000000000000.0;

hoặc là

float i = 1000000000000000000.0f;

Loại doublefloatcó độ chính xác hạn chế, vì vậy nếu bạn lấy một giá trị doublehoặc floatgiá trị đủ lớn , việc thêm 1vào nó sẽ dẫn đến cùng một giá trị.


10
Hoặc là (double)(1L<<53) - hoặcfloat i = (float)(1<<24)
dave_thompson_085,

3
@Ruslan: Bất kỳ nhà toán học nào cũng sẽ không đồng ý. Số dấu chấm động có rất ít ý nghĩa. Chúng không liên kết, không phản xạ (NaN! = NaN) và thậm chí không thể thay thế (-0 == 0, nhưng 1/0! = 1 / -0). Vì vậy, hầu hết các máy móc của đại số là không thể áp dụng được.
Kevin

2
@Kevin trong khi các số dấu phẩy động thực sự không thể có ý nghĩa quá nhiều nói chung, hành vi của số vô hạn (được mô tả trong câu đó) được thiết kế để có ý nghĩa.
Ruslan

4
@Kevin Công bằng mà nói, nếu bạn xử lý các giá trị vô hạn hoặc không xác định, bạn cũng không thể giả định các thuộc tính mà bạn đã liệt kê trong đại số.
Voo

2
@Kevin: IMHO, phép toán dấu phẩy động có thể có ý nghĩa hơn rất nhiều nếu chúng thay thế các khái niệm về "số 0 dương và âm" bằng dấu dương, âm và không dấu "trong số 0" cùng với một "số 0 thực", và NaN bằng chính nó. Số 0 thực có thể hoạt động như một danh tính cộng thêm trong mọi trường hợp và một cái gì đó liên quan đến phép chia cho các số vô cùng sẽ mất đi sự thiên vị của chúng đối với việc giả sử các số vô cùng là dương.
supercat

64

Những câu đố này được mô tả chi tiết trong cuốn sách "Java Puzzlers: Bẫy, Cạm bẫy và Vụ án góc" của Joshua Bloch và Neal Gafter.

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

hoặc là:

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

cả hai sẽ dẫn đến một vòng lặp vô hạn, bởi vì việc thêm 1vào một giá trị dấu phẩy động đủ lớn sẽ không thay đổi giá trị, vì nó không "thu hẹp khoảng cách" với giá trị kế thừa của nó 1 .

Lưu ý về câu đố thứ hai (dành cho độc giả tương lai):

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

cũng dẫn đến một vòng lặp vô hạn, vì NaN không bằng bất kỳ giá trị dấu phẩy động nào, kể cả chính nó 2 .


1 - Câu đố Java: Bẫy, Cạm bẫy và Trường hợp góc (chương 4 - Câu đố lặp lại).

2 - JLS §15.21.1



0

Chỉ là một ý tưởng: còn boolean thì sao?

bool i = TRUE;

Đây không phải là một trường hợp ở đâu i + 1 == i?


phụ thuộc vào ngôn ngữ. Nhiều ngôn ngữ tự động ép buộc các boolean thành int khi được kết hợp với một int. Những người khác làm như bạn đề xuất - ép buộc int thành boolean.
Carl Witthoft

8
Câu hỏi này là một câu hỏi Java và đề xuất của bạn không vượt qua quá trình biên dịch trong Java (không có +toán tử nào nhận một booleanvà một intlàm toán hạng).
Eran

@Eran: đó là toàn bộ ý tưởng về việc nạp chồng toán tử. Bạn có thể làm cho các boolean Java hoạt động giống như các boolean trong C ++.
Dominique

4
Ngoại trừ việc Java không hỗ trợ nạp chồng toán tử, vì vậy bạn không thể.
CupawnTae
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.