Para qual valor de i faz o loop while (i == i + 1) {} para sempre?

120

Corri através deste quebra-cabeças de um curso de programação avançada em um exame universitário do Reino Unido .

Considere o seguinte loop, no qual i é, até agora, não declarado:

while (i == i + 1) {}

Encontre a definição de i, que precede esse loop, de modo que o loop while continue para sempre.

A próxima pergunta, que fez a mesma pergunta para este trecho de código:

while (i != i) {}

era óbvio para mim. É claro que nesta outra situação é, NaNmas estou realmente preso à anterior. Isso tem a ver com excesso? O que causaria esse loop para sempre em Java?

jake mckenzie
fonte
3
Alguma possibilidade de substituir o .equals()método? Como i não é declarado, podemos usar qualquer classe do que queremos.
Geno Chen
7
@Raedwald estudando código "pouco profissional" torna-lo mais "profissional", então ... Enfim, é uma boa pergunta
Andrew Tobilko
12
De fato, em C # isso também funciona para tipos numéricos anuláveis ​​cujos valores são null, uma vez que null == nullé verdadeiro e null + 1é null.
precisa
4
@ EricDuminil: A situação é muito pior do que você imagina. Em muitas linguagens, a aritmética de ponto flutuante deve ser feita com pelo menos 64 bits de precisão especificados por um duplo, o que significa que pode ser feito com maior precisão ao capricho do compilador e , na prática, isso acontece . Posso apontar para você uma dúzia de perguntas neste site de programadores em C # que estão se perguntando por que 0.2 + 0.1 == 0.3altera seu valor dependendo das configurações do compilador, da fase da lua e assim por diante.
Eric Lippert
5
@EricDuminil: A culpa dessa bagunça recai sobre a Intel, que nos forneceu um chipset que faz aritmética de ponto flutuante de maior precisão e rapidez se os números puderem ser registrados, o que significa que os resultados de um cálculo de ponto flutuante podem alterar seus valores dependendo sobre como o agendador de registros no otimizador funciona hoje. Suas escolhas como designer de linguagem são, então, entre cálculos repetíveis e cálculos rápidos e precisos , e a comunidade que se preocupa com a matemática de ponto flutuante optará pelo último.
precisa

Respostas:

142

Antes de tudo, como o while (i == i + 1) {}loop não altera o valor de i, tornar esse loop infinito é equivalente a escolher um valor isatisfatório i == i + 1.

Existem muitos desses valores:

Vamos começar com os "exóticos":

double i = Double.POSITIVE_INFINITY;

ou

double i =  Double.NEGATIVE_INFINITY;

A razão para a satisfação desses valores i == i + 1é declarada em
JLS 15.18.2. Operadores aditivos (+ e -) para tipos numéricos :

A soma de um infinito e um valor finito é igual ao operando infinito.

Isso não é surpreendente, pois a adição de um valor finito a um valor infinito deve resultar em um valor infinito.

Dito isto, a maioria dos valores ique satisfazem i == i + 1são simplesmente valores grandes double(ou float):

Por exemplo:

double i = Double.MAX_VALUE;

ou

double i = 1000000000000000000.0;

ou

float i = 1000000000000000000.0f;

Os tipos doublee floattêm precisão limitada; portanto, se você usar um valor doubleou floatvalor suficientemente grande , a adição 1a ele resultará no mesmo valor.

Eran
fonte
10
Ou (double)(1L<<53)- oufloat i = (float)(1<<24)
dave_thompson_085 28/11
3
@Ruslan: Qualquer matemático discordaria. Números de ponto flutuante fazem muito pouco sentido. Eles são não-associativos, não reflexivos (NaN! = NaN) e nem substituíveis (-0 == 0, mas 1/0! = 1 / -0). Portanto, a maioria das máquinas de álgebra é inaplicável.
28418 Kevin
2
@ Kevin enquanto os números de ponto flutuante não podem realmente fazer muito sentido em geral, o comportamento dos infinitos (que é o que é descrito nessa frase) foi projetado para fazer sentido.
Ruslan
4
@ Kevin Para ser justo com carros alegóricos, se você lida com infinitos ou valores indefinidos, também não pode assumir as propriedades que listou em álgebra.
Voo
2
@Kevin: IMHO, a matemática de ponto flutuante poderia ter feito muito mais sentido se eles substituíssem os conceitos de "zero positivo e negativo", sinalizando "infinitesimais" positivos, negativos e não assinados, juntamente com um "zero verdadeiro", e NaN igual a si mesmo. O zero verdadeiro pode se comportar como uma identidade aditiva em todos os casos, e alguma operação envolvendo divisão por infinitesimais perderia seu viés no sentido de assumir que os infinitesimais são positivos.
Supercat
64

Esses quebra-cabeças são descritos em detalhes no livro "Java Puzzlers: Traps, Pitfalls and Corner Cases", de Joshua Bloch e Neal Gafter.

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

ou:

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

ambos resultarão em um loop infinito, porque a adição 1a um valor de ponto flutuante suficientemente grande não mudará o valor, porque não "preenche a lacuna" para seu sucessor 1 .

Uma observação sobre o segundo quebra-cabeça (para futuros leitores):

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

também resulta em um loop infinito, porque NaN não é igual a nenhum valor de ponto flutuante, incluindo ele próprio 2 .


1 - Java Puzzlers: armadilhas, armadilhas e casos de canto (capítulo 4 - Loopy Puzzlers).

2 - JLS §15.21.1

Oleksandr Pyrohov
fonte
1

double i = Double.POSITIVE_INFINITY;

Farcas George
fonte
0

Apenas uma idéia: e os booleanos?

bool i = TRUE;

Não é este o caso i + 1 == i?

Dominique
fonte
depende do idioma. Muitos idiomas coagem automaticamente os booleanos a ints quando combinados com um int. Outros fazem o que você sugere - forçando o int a um booleano.
24518 Carl Witthoft
8
Esta pergunta é uma pergunta sobre Java e sua sugestão não passa na compilação em Java (que não possui um +operador que use a booleane a intcomo operandos).
Eran
@ Eran: essa é toda a ideia de sobrecarga do operador. Você pode fazer com que os booleanos Java se comportem como os C ++.
Dominique
4
Exceto que o Java não suporta sobrecarga do operador, você não pode.
CupawnTae