Для якого значення i робить цикл while (i == i + 1) {}?


120

Я перебігав цю загадку з курсу просунутого програмування на іспиті в університеті Великобританії .

Розглянемо наступний цикл, в якому я, до цих пір, незадекларований:

while (i == i + 1) {}

Знайдіть визначення i, яке передує цьому циклу, таким, що цикл while продовжується назавжди.

Наступне запитання, яке задало те саме питання для цього фрагмента коду:

while (i != i) {}

для мене було очевидно. Звичайно, в цій іншій ситуації це так, NaNале я дійсно застряг на попередній. Це має відношення до переповнення? Що призведе до того, що така петля назавжди зациклюється на Java?


3
Будь-які можливості замінити .equals()метод? Оскільки я незадекларований, ми можемо використовувати будь-який клас того, що хочемо.
Geno Chen

7
@Raedwald вивчення "непрофесійного" коду робить вас більш "професійними", тож ... У всякому разі, це гарне питання
Андрій Тобілко

12
Приємно, що в C # це також працює для змінних числових типів, значення яких є null, оскільки null == nullце правда і null + 1є null.
Ерік Ліпперт

4
@EricDuminil: Ситуація набагато гірша, ніж ти уявляєш. У багатьох мовах арифметику з плаваючою комою потрібно виконувати принаймні на 64 біта точності, визначених подвійною, а це означає, що це можна зробити з більш високою точністю за примхою компілятора, і на практиці це відбувається . Я можу вказати вам на десяток запитань на цьому сайті від програмістів C #, які цікавляться, чому 0.2 + 0.1 == 0.3змінюється його значення залежно від налаштувань компілятора, фази Місяця тощо.
Ерік Ліпперт

5
@EricDuminil: Вина в цьому безладі лягає на Intel, який надав нам набір мікросхем, який робить більш високу точність і швидшу арифметику з плаваючою комою, якщо числа можна зареєструвати, а це означає, що результати обчислення плаваючої точки можуть змінювати свої значення залежно про те, наскільки добре працює планувальник реєстру в оптимізаторі сьогодні. Тоді ваш вибір як дизайнера мови відбувається між повторюваними обчисленнями та швидкими, точними обчисленнями , і спільнота, яка піклується про математику з плаваючою комою, вирішить останнє.
Ерік Ліпперт

Відповіді:


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
@ Руслан: Будь-який математик не погодиться. Числа з плаваючою комою мають дуже мало сенсу. Вони неасоціативні, нерефлексивні (NaN! = NaN) і навіть не замінюються (-0 == 0, але 1/0! = 1 / -0). Тож більшість механізмів алгебри непридатні.
Кевін

2
@Kevin, хоча числа з плаваючою комою насправді не можуть мати занадто великого сенсу в цілому, поведінка нескінченностей (саме те, що описано в цьому реченні) було покликане мати сенс.
Руслан

4
@Kevin Щоб бути справедливим до плаває, якщо ви маєте справу з нескінченностями або невизначеними значеннями, ви також не можете припустити властивості, які ви вказали в алгебрі.
Voo

2
@Kevin: ІМХО, математика з плаваючою комою могла б мати набагато більше сенсу, якби вони замінили поняття "позитивний і негативний нуль" знаком позитивного, негативного та непідписаного "нескінченнихмалій" разом з одним "справжнім нулем", і зробили NaN рівний собі. Справжній нуль може вести себе як адитивна ідентичність у всіх випадках, і щось, що передбачає поділ на нескінченних тварин, втратить свою упередженість щодо того, щоб припустити, що нескінченні тварини позитивні.
supercat

64

Ці головоломки докладно описані в книзі Джошуа Блоха та Ніла Гафтера "Пазли Java: Пастки, підводні камені та кутові випадки".

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) {}

також призводить до нескінченного циклу, оскільки NaN не дорівнює жодному значенню з плаваючою комою, включаючи себе 2 .


1 - Загадки Java: пастки, підводні камені та кутові випадки (глава 4 - Loopy Puzzlers).

2 - JLS §15.21.1



0

Просто ідея: що з булевими?

bool i = TRUE;

Хіба це не випадок, коли i + 1 == i?


залежить від мови. Багато мов автоматично примушують булеви до int у поєднанні з int. Інші роблять так, як ви пропонуєте - примушуючи Int до булевого.
Карл Віттофт

8
Це питання є питанням Java, і ваша пропозиція не проходить компіляцію в Java (у якої немає +оператора, який приймає операнди a booleanі a int).
Еран

@Eran: у цьому вся ідея перевантаження оператора. Ви можете змусити booleans Java вести себе як C ++.
Домінік

4
За винятком того, що Java не підтримує перевантаження оператора, тому ви не можете.
CupawnTae
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.