¿Para qué valor de i hace while (i == i + 1) {} bucle para siempre?

120

Crucé este rompecabezas de un curso de programación avanzada en un examen universitario del Reino Unido .

Considere el siguiente ciclo, en el que, hasta ahora, no está declarado:

while (i == i + 1) {}

Encuentre la definición de i, que precede a este bucle, de modo que el bucle while continúe para siempre.

La siguiente pregunta, que hizo la misma pregunta para este fragmento de código:

while (i != i) {}

fue obvio para mí. Por supuesto que en esta otra situación lo es, NaNpero estoy realmente atascado en la anterior. ¿Tiene esto que ver con el desbordamiento? ¿Qué haría que tal bucle se repita para siempre en Java?

jake mckenzie
fuente
3
¿Alguna posibilidad de anular el .equals()método? Dado que i no está declarado, podemos usar cualquier clase de lo que queramos.
Geno Chen
7
@Raedwald estudiar el código "no profesional" te hace más "profesional", así que ... De todos modos, es una buena pregunta
Andrew Tobilko
12
Dato curioso, en C # esto también funciona para tipos numéricos que aceptan valores NULL cuyos valores son null, ya que null == nulles verdadero y null + 1es null.
Eric Lippert
4
@EricDuminil: La situación es mucho peor de lo que imagina. En muchos lenguajes, la aritmética de coma flotante debe realizarse en al menos los 64 bits de precisión especificados por un doble, lo que significa que se puede hacer con mayor precisión a voluntad del compilador, y en la práctica esto sucede . Puedo señalarle una docena de preguntas en este sitio de programadores de C # que se preguntan por qué 0.2 + 0.1 == 0.3cambia su valor según la configuración del compilador, la fase de la luna, etc.
Eric Lippert
5
@EricDuminil: La culpa de este lío recae en Intel, quien nos dio un conjunto de chips que hace aritmética de punto flotante de mayor precisión y más rápida si los números se pueden registrar, lo que significa que los resultados de un cálculo de punto flotante pueden cambiar sus valores dependiendo sobre qué tan bien funciona hoy el programador de registros en el optimizador. Sus opciones como diseñador de lenguaje son entonces entre cálculos repetibles y cálculos rápidos y precisos , y la comunidad que se preocupa por las matemáticas de punto flotante optará por lo último.
Eric Lippert

Respuestas:

142

En primer lugar, dado que el while (i == i + 1) {}ciclo no cambia el valor de i, hacer que este ciclo sea infinito es equivalente a elegir un valor de ique satisfaga i == i + 1.

Hay muchos valores de este tipo:

Empecemos por los "exóticos":

double i = Double.POSITIVE_INFINITY;

o

double i =  Double.NEGATIVE_INFINITY;

La razón por la que estos valores son satisfactorios i == i + 1se establece en
JLS 15.18.2. Operadores aditivos (+ y -) para tipos numéricos :

La suma de un valor infinito y finito es igual al operando infinito.

Esto no es sorprendente, ya que agregar un valor finito a un valor infinito debería resultar en un valor infinito.

Dicho esto, la mayoría de los valores ique satisfacen i == i + 1son simplemente valores grandes double(o float):

Por ejemplo:

double i = Double.MAX_VALUE;

o

double i = 1000000000000000000.0;

o

float i = 1000000000000000000.0f;

Los tipos doubley floattienen una precisión limitada, por lo que si toma un valor doubleo lo suficientemente grande float, agregarlo 1dará como resultado el mismo valor.

Eran
fuente
10
O (double)(1L<<53)- ofloat i = (float)(1<<24)
dave_thompson_085
3
@Ruslan: Cualquier matemático no estaría de acuerdo. Los números de coma flotante tienen muy poco sentido. Son no asociativos, no reflexivos (NaN! = NaN) y ni siquiera sustituibles (-0 == 0, pero 1/0! = 1 / -0). De modo que la mayor parte de la maquinaria del álgebra es inaplicable.
Kevin
2
@Kevin, si bien los números de punto flotante no pueden tener mucho sentido en general, el comportamiento de los infinitos (que es lo que se describe en esa oración) fue diseñado para tener sentido.
Ruslan
4
@Kevin Para ser justos con los flotantes, si manejas infinitos o valores indefinidos tampoco puedes asumir las propiedades que enumeraste en álgebra.
Voo
2
@Kevin: En mi humilde opinión, las matemáticas de punto flotante podrían haber tenido mucho más sentido si hubieran reemplazado los conceptos de "cero positivo y negativo" signo positivo, negativo y "infinitesimales" sin signo junto con un "cero verdadero", y hubieran hecho NaN igual a sí mismo. El cero verdadero podría comportarse como una identidad aditiva en todos los casos, y las operaciones que implican división por infinitesimales perderían su sesgo hacia suponer que los infinitesimales son positivos.
supercat
64

Estos rompecabezas se describen en detalle en el libro "Java Puzzlers: Traps, Pitfalls, and Corner Cases" de Joshua Bloch y Neal Gafter.

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

o:

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

ambos darán como resultado un bucle infinito, porque agregar 1a un valor de coma flotante que sea suficientemente grande no cambiará el valor, porque no "cierra la brecha" con su sucesor 1 .

Una nota sobre el segundo acertijo (para futuros lectores):

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

también da como resultado un bucle infinito, porque NaN no es igual a ningún valor de punto flotante, incluido él mismo 2 .


1 - Rompecabezas de Java: trampas, trampas y casos de esquina (capítulo 4 - Rompecabezas de loopy).

2 - JLS §15.21.1

Oleksandr Pyrohov
fuente
1

double i = Double.POSITIVE_INFINITY;

Farcas George
fuente
0

Solo una idea: ¿qué pasa con los booleanos?

bool i = TRUE;

¿No es este un caso donde i + 1 == i?

Dominique
fuente
depende del idioma. Muchos lenguajes convierten automáticamente los valores booleanos en ints cuando se combinan con un int. Otros hacen lo que sugieres: coaccionando el int a un booleano.
Carl Witthoft
8
Esta pregunta es una pregunta de Java, y su sugerencia no pasa la compilación en Java (que no tiene un +operador que tome a booleany a intcomo operandos).
Eran
@Eran: esa es la idea de la sobrecarga de operadores. Puede hacer que los booleanos de Java se comporten como los de C ++.
Dominique
4
Excepto que Java no admite la sobrecarga de operadores, por lo que no puede.
CupawnTae