Per quale valore di i fa il ciclo while (i == i + 1) {} per sempre?


120

Mi sono imbattuto in questo puzzle da un corso di programmazione avanzata a un esame universitario del Regno Unito .

Considera il seguente ciclo, in cui i è, finora, non dichiarato:

while (i == i + 1) {}

Trova la definizione di i, che precede questo ciclo, in modo tale che il ciclo while continui per sempre.

La domanda successiva, che ha posto la stessa domanda per questo frammento di codice:

while (i != i) {}

era ovvio per me. Ovviamente in quest'altra situazione lo è, NaNma sono davvero bloccato su quella precedente. Questo ha a che fare con l'overflow? Cosa potrebbe causare un ciclo del genere per sempre in Java?


3
Eventuali possibilità di sovrascrivere il .equals()metodo? Poiché i non è dichiarato, possiamo usare qualsiasi classe di ciò che vogliamo.
Geno Chen

7
@ Raedwald studiare il codice "non professionale" ti rende più "professionale", quindi ... Comunque, è una buona domanda
Andrew Tobilko

12
Fatto divertente, in C # questo funziona anche per i tipi numerici nullable i cui valori sono null, poiché null == nullè vero e null + 1è null.
Eric Lippert

4
@EricDuminil: La situazione è molto peggiore di quanto immagini. In molti linguaggi, l'aritmetica in virgola mobile deve essere eseguita con almeno i 64 bit di precisione specificati da un double, il che significa che può essere eseguita con maggiore precisione per capriccio del compilatore, e in pratica questo accade . Posso indicarti una dozzina di domande su questo sito da programmatori C # che si stanno chiedendo perché 0.2 + 0.1 == 0.3cambia il suo valore a seconda delle impostazioni del compilatore, della fase lunare e così via.
Eric Lippert

5
@EricDuminil: La colpa di questo pasticcio ricade su Intel, che ci ha fornito un chipset che esegue calcoli in virgola mobile più precisi e più veloci se i numeri possono essere registrati, il che significa che i risultati di un calcolo in virgola mobile possono cambiare i loro valori a seconda su come funziona oggi lo scheduler di registro nell'ottimizzatore. Le tue scelte come progettista di linguaggi sono quindi tra calcoli ripetibili e calcoli veloci e precisi , e la comunità che si occupa della matematica in virgola mobile opterà per quest'ultimo.
Eric Lippert

Risposte:


142

Prima di tutto, poiché il while (i == i + 1) {}ciclo non cambia il valore di i, rendere infinito questo ciclo equivale a scegliere un valore iche soddisfi i == i + 1.

Ci sono molti di questi valori:

Cominciamo con quelli "esotici":

double i = Double.POSITIVE_INFINITY;

o

double i =  Double.NEGATIVE_INFINITY;

Il motivo per cui questi valori soddisfano i == i + 1è indicato in
JLS 15.18.2. Operatori additivi (+ e -) per i tipi numerici :

La somma di un valore infinito e finito è uguale all'operando infinito.

Ciò non sorprende, poiché l'aggiunta di un valore finito a un valore infinito dovrebbe risultare in un valore infinito.

Detto questo, la maggior parte dei valori di iquel soddisfare i == i + 1sono semplicemente valori grandi double(o float):

Per esempio:

double i = Double.MAX_VALUE;

o

double i = 1000000000000000000.0;

o

float i = 1000000000000000000.0f;

I tipi doublee floathanno una precisione limitata, quindi se prendi un valore doubleo abbastanza grande float, aggiungendolo 1si otterrà lo stesso valore.


10
Oppure (double)(1L<<53)- ofloat i = (float)(1<<24)
dave_thompson_085

3
@Ruslan: qualsiasi matematico non sarebbe d'accordo. I numeri in virgola mobile non hanno molto senso. Sono non associative, non riflessive (NaN! = NaN) e nemmeno sostituibili (-0 == 0, ma 1/0! = 1 / -0). Quindi la maggior parte dei meccanismi algebrici è inapplicabile.
Kevin

2
@Kevin mentre i numeri in virgola mobile non possono davvero avere molto senso in generale, il comportamento degli infiniti (che è ciò che è descritto in quella frase) è stato progettato per avere un senso.
Ruslan

4
@Kevin Per essere onesti con i float, se hai a che fare con infiniti o valori indefiniti non puoi assumere le proprietà che hai elencato in algebra.
Voo

2
@ Kevin: IMHO, la matematica in virgola mobile avrebbe potuto avere molto più senso se avessero sostituito i concetti di "zero positivo e negativo" segno positivo, negativo e "infinitesimali" senza segno insieme a uno "zero vero", e NaN uguale a se stesso. Il vero zero potrebbe comportarsi come un'identità additiva in tutti i casi, e le operazioni di qualcosa che implicano la divisione per infinitesimi perderebbero la loro tendenza a presumere che gli infinitesimi siano positivi.
supercat

64

Questi puzzle sono descritti in dettaglio nel libro "Java Puzzlers: Traps, Pitfalls, and Corner Cases" di Joshua Bloch e Neal Gafter.

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

o:

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

entrambi risulteranno in un ciclo infinito, perché l'aggiunta 1a un valore in virgola mobile che è sufficientemente grande non cambierà il valore, perché non "colma il divario" al suo successore 1 .

Una nota sul secondo puzzle (per i futuri lettori):

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

si traduce anche in un ciclo infinito, perché NaN non è uguale a nessun valore a virgola mobile, incluso se stesso 2 .


1 - Java Puzzlers: Traps, Pitfalls e Corner Cases (capitolo 4 - Loopy Puzzlers).

2 - JLS §15.21.1



0

Solo un'idea: che dire dei booleani?

bool i = TRUE;

Non è questo un caso in cui i + 1 == i?


dipende dalla lingua. Molte lingue costringono automaticamente i booleani a int se combinati con un int. Altri fanno come suggerisci, costringendo l'int a un booleano.
Carl Witthoft

8
Questa domanda è una domanda Java e il tuo suggerimento non supera la compilazione in Java (che non ha +operatori che accettano a booleane an intcome operandi).
Eran

@Eran: questa è l'intera idea del sovraccarico dell'operatore. Puoi fare in modo che i booleani Java si comportino come quelli C ++.
Dominique

4
Tranne che Java non supporta il sovraccarico dell'operatore, quindi non puoi.
CupawnTae
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.