Quelle est la valeur de i while (i == i + 1) {} boucle indéfiniment?

120

J'ai traversé ce casse-tête à partir d'un cours de programmation avancé lors d'un examen universitaire britannique .

Considérez la boucle suivante, dans laquelle i est, jusqu'à présent, non déclaré:

while (i == i + 1) {}

Trouvez la définition de i, qui précède cette boucle, de telle sorte que la boucle while continue pour toujours.

La question suivante, qui posait la même question pour cet extrait de code:

while (i != i) {}

était évident pour moi. Bien sûr, dans cette autre situation, c'est le cas, NaNmais je suis vraiment coincé sur la précédente. Est-ce que cela a à voir avec le débordement? Pourquoi une telle boucle ferait-elle une boucle éternelle en Java?

jake mckenzie
la source
3
Des possibilités de remplacer la .equals()méthode? Puisque i n'est pas déclaré, nous pouvons utiliser n'importe quelle classe de ce que nous voulons.
Geno Chen
7
@Raedwald étudier le code "non professionnel" vous rend plus "professionnel", alors ... Bref, c'est une bonne question
Andrew Tobilko
12
Fait amusant, en C #, cela fonctionne également pour les types numériques Nullable dont les valeurs sont null, puisque null == nullest vrai, et null + 1est null.
Eric Lippert
4
@EricDuminil: La situation est bien pire que vous ne l'imaginez. Dans de nombreux langages, l'arithmétique en virgule flottante doit être effectuée avec au moins les 64 bits de précision spécifiés par un double, ce qui signifie qu'elle peut être effectuée avec une précision plus élevée au gré du compilateur, et en pratique cela se produit . Je peux vous orienter vers une dizaine de questions sur ce site de la part de programmeurs C # qui se demandent pourquoi 0.2 + 0.1 == 0.3change sa valeur en fonction des paramètres du compilateur, de la phase de la lune, etc.
Eric Lippert
5
@EricDuminil: La responsabilité de ce désordre incombe à Intel, qui nous a donné un jeu de puces qui effectue une arithmétique en virgule flottante plus précise et plus rapide si les nombres peuvent être enregistrés, ce qui signifie que les résultats d'un calcul en virgule flottante peuvent changer leurs valeurs en fonction sur le fonctionnement actuel du planificateur de registre dans l'optimiseur. Vos choix en tant que concepteur de langage se situent alors entre des calculs répétables et des calculs rapides et précis , et la communauté qui se soucie des mathématiques en virgule flottante optera pour ce dernier.
Eric Lippert

Réponses:

142

Tout d'abord, puisque la while (i == i + 1) {}boucle ne change pas la valeur de i, rendre cette boucle infinie équivaut à choisir une valeur de iqui satisfait i == i + 1.

Il existe de nombreuses valeurs de ce type:

Commençons par les "exotiques":

double i = Double.POSITIVE_INFINITY;

ou

double i =  Double.NEGATIVE_INFINITY;

La raison pour laquelle ces valeurs satisfonti == i + 1 est indiquée dans
JLS 15.18.2. Opérateurs additifs (+ et -) pour les types numériques :

La somme d'une infinité et d'une valeur finie est égale à l'opérande infini.

Cela n'est pas surprenant, car l'ajout d'une valeur finie à une valeur infinie devrait entraîner une valeur infinie.

Cela dit, la plupart des valeurs de iqui satisfont i == i + 1sont simplement des valeurs grandes double(ou float):

Par exemple:

double i = Double.MAX_VALUE;

ou

double i = 1000000000000000000.0;

ou

float i = 1000000000000000000.0f;

Les types doubleet floatont une précision limitée, donc si vous prenez une valeur doubleou assez grande float, l'ajout 1de celle-ci donnera la même valeur.

Eran
la source
10
Ou (double)(1L<<53)- oufloat i = (float)(1<<24)
dave_thompson_085
3
@Ruslan: N'importe quel mathématicien serait en désaccord. Les nombres à virgule flottante ont très peu de sens. Ils sont non associatifs, non réflexifs (NaN! = NaN), et même pas substituables (-0 == 0, mais 1/0! = 1 / -0). Ainsi, la plupart des mécanismes de l'algèbre sont inapplicables.
Kevin
2
@Kevin alors que les nombres à virgule flottante ne peuvent en effet pas avoir trop de sens en général, le comportement des infinis (qui est ce qui est décrit dans cette phrase) a été conçu pour avoir un sens.
Ruslan
4
@Kevin Pour être juste envers les flottants, si vous traitez avec des infinis ou des valeurs indéfinies, vous ne pouvez pas non plus assumer les propriétés que vous avez répertoriées dans l'algèbre.
Voo
2
@Kevin: À mon humble avis, les mathématiques en virgule flottante auraient pu avoir beaucoup plus de sens si elles avaient remplacé les concepts de signe "zéro positif et négatif" positif, négatif et non signé "infinitésimales" avec un "vrai zéro", et NaN égal à lui-même. Le vrai zéro pourrait se comporter comme une identité additive dans tous les cas, et des opérations impliquant une division par des infinitésimales perdraient leur biais en supposant que les infinitésimaux sont positifs.
supercat
64

Ces puzzles sont décrits en détail dans le livre "Java Puzzlers: Traps, Pitfalls, and Corner Cases" de Joshua Bloch et Neal Gafter.

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

ou:

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

les deux résulteront en une boucle infinie, car l'ajout 1d'une valeur à virgule flottante suffisamment grande ne changera pas la valeur, car elle ne «comble pas l'écart» avec son successeur 1 .

Une note sur le deuxième puzzle (pour les futurs lecteurs):

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

entraîne également une boucle infinie, car NaN n'est égal à aucune valeur à virgule flottante, y compris lui-même 2 .


1 - Puzzlers Java: pièges, pièges et cas de coin (chapitre 4 - Puzzlers Loopy).

2 - JLS §15.21.1

Oleksandr Pyrohov
la source
1

double i = Double.POSITIVE_INFINITY;

Farcas George
la source
0

Juste une idée: qu'en est-il des booléens?

bool i = TRUE;

N'est-ce pas un cas où i + 1 == i?

Dominique
la source
dépend de la langue. De nombreux langages contraignent automatiquement les booléens en entiers lorsqu'ils sont combinés avec un int. D'autres font ce que vous suggérez - en forçant l'int en un booléen.
Carl Witthoft
8
Cette question est une question Java, et votre suggestion ne passe pas la compilation en Java (qui n'a pas d' +opérateur qui prend a booleanet an intcomme opérandes).
Eran
@Eran: c'est toute l'idée de la surcharge des opérateurs. Vous pouvez faire en sorte que les booléens Java se comportent comme ceux de C ++.
Dominique
4
Sauf que Java ne prend pas en charge la surcharge d'opérateurs, vous ne pouvez donc pas.
CupawnTae