Questo ciclo "for" si interrompe e perché / perché no? for (var i = 0; 1 / i> 0; i ++) {}


104

Questo forciclo si ferma mai?

for (var i=0; 1/i > 0; i++) {
}

Se è così, quando e perché? Mi è stato detto che si ferma, ma non mi è stato dato alcun motivo.

Upddate

Come parte dell'indagine, ho scritto un articolo piuttosto lungo e dettagliato che spiega tutto ciò che sta succedendo sotto il cofano: ecco cosa devi sapere sul tipo di numero di JavaScript


5
Non si fermerà. prova a eseguire questo pezzo di codice. for (var i = 0; 1 / i> 0; i ++) {console.log (i)}
Sourabh Agrawal


3
Number.MAX_VALUE + 9.979202e291 == "Infinity" e 1 / (NaN o 'Infinity' o 'undefined')> 0 == false.
askeet

6
Javascript ignorerà quel ciclo perché non ha istruzioni all'interno? cioè ottimizzarlo? So che ci sono alcuni linguaggi compilati che lo farebbero.
Brian J

3
@askeet, come gotnull e altri sottolineano di seguito, non raggiungiamo mai Infinity incrementando ripetutamente, ma rimanendo intrappolati in un loop dopo Number.MAX_SAFE_INTEGER + 1.
LSpice

Risposte:


128

(Non sono un fan dei meta-contenuti, ma: le risposte di gotnull e le_m sono sia corrette che utili. Erano originariamente, e lo sono ancora di più con le modifiche apportate dopo la pubblicazione di questo Wiki della community. La motivazione originale per questo CW è in gran parte scomparso come risultato di queste modifiche, ma rimane utile, quindi ... Inoltre: sebbene ci siano solo un paio di autori elencati, molti altri membri della comunità hanno aiutato molto con i commenti che sono stati inseriti e ripuliti. non è solo un CW di nome.)


Il ciclo non si interromperà in un motore JavaScript implementato correttamente. (L'ambiente host del motore potrebbe eventualmente terminarlo perché è infinito, ma questa è un'altra cosa.)

Ecco perché:

  1. Inizialmente, quando iè 0, la condizione 1/i > 0è vera perché in JavaScript 1/0è Infinityed Infinity > 0è vera.

  2. Dopodiché, iverrà incrementato e continuerà a crescere come valore intero positivo per un lungo periodo (ulteriori 9,007,199,254,740,991 iterazioni). In tutti questi casi, 1/irimarrà > 0(sebbene i valori per 1/idiventino veramente piccoli verso la fine!) E così il ciclo continua fino al ciclo compreso dove iraggiunge il valore Number.MAX_SAFE_INTEGER.

  3. I numeri in JavaScript sono IEEE-754 in virgola mobile a doppia precisione, un formato abbastanza compatto (64 bit) che fornisce calcoli veloci e una vasta gamma. Lo fa memorizzando il numero come un segno bit, un esponente di 11 bit e un significando a 52 bit (sebbene attraverso l'intelligenza in realtà ottiene 53 bit di precisione). È binario (base 2) in virgola mobile: il significando (più un po 'di intelligenza) ci dà il valore e l'esponente ci dà la grandezza del numero.

    Naturalmente, con così tanti bit significativi, non tutti i numeri possono essere memorizzati. Qui è il numero 1, e il successivo numero più alto dopo 1 che il formato in grado di memorizzare, 1 + 2 -52 ≈ 1,00000000000000022, e il successivo più alto, dopo che 1 + 2 × 2 -52 ≈ 1,00000000000000044:

       + ------------------------------------------------- -------------- segno bit
      / + ------- + ---------------------------------------- -------------- esponente
     / / | + ------------------------------------------------- + - significand
    / / | / |
    0 01111111111 00000000000000000000000000000000000000000000000000
                    = 1
    0 01111111111 00000000000000000000000000000000000000000000000001
                    ≈ 1.00000000000000022
    0 01111111111 0000000000000000000000000000000000000000000000000010
                    ≈ 1.00000000000000044
    

    Notare il salto da 1.00000000000000022 a 1.00000000000000044; non c'è modo di memorizzare 1.0000000000000003. Ciò può accadere anche con i numeri interi: Number.MAX_SAFE_INTEGER(9,007,199,254,740,991) è il valore intero positivo più alto che il formato può contenere ie i + 1sono entrambi esattamente rappresentabili ( spec ). Possono essere rappresentati sia 9.007.199.254.740.991 che 9.007.199.254.740.992, ma il numero intero successivo , 9.007.199.254.740.993, non può; il numero intero successivo che possiamo rappresentare dopo 9.007.199.254.740.992 è 9.007.199.254.740.994. Di seguito sono riportati i modelli di bit, nota il bit più a destra (meno significativo):

       + ------------------------------------------------- -------------- segno bit
      / + ------- + ---------------------------------------- -------------- esponente
     / / | + ------------------------------------------------- + - significand
    / / | / |
    0 10000110011 1111111111111111111111111111111111111111111111111111
                    = 9007199254740991 (Number.MAX_SAFE_INTEGER)
    0 10000110100 00000000000000000000000000000000000000000000000000
                    = 9007199254740992 (Number.MAX_SAFE_INTEGER + 1)
    x xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                      9007199254740993 (Number.MAX_SAFE_INTEGER + 2) non può essere memorizzato
    0 10000110100 0000000000000000000000000000000000000000000000000001
                    = 9007199254740994 (Number.MAX_SAFE_INTEGER + 3)
    

    Ricorda, il formato è in base 2 e con quell'esponente il bit meno significativo non è più frazionario; ha un valore di 2. Può essere disattivato (9.007.199.254.740.992) o attivo (9.007.199.254.740.994); quindi a questo punto, abbiamo iniziato a perdere precisione anche sulla scala dei numeri interi (interi). Che ha implicazioni per il nostro ciclo!

  4. Dopo aver completato il i = 9,007,199,254,740,992ciclo, i++ci dà ... di i = 9,007,199,254,740,992nuovo; non ci sono cambiamenti i, perché il numero intero successivo non può essere memorizzato e il calcolo finisce per arrotondare per difetto. icambierebbe se lo facessimo i += 2, ma i++non possiamo cambiarlo. Quindi abbiamo raggiunto lo stato stazionario: inon cambia mai e il ciclo non termina mai.

Ecco i vari calcoli rilevanti:

if (!Number.MAX_SAFE_INTEGER) {
  // Browser doesn't have the Number.MAX_SAFE_INTEGER
  // property; shim it. Should use Object.defineProperty
  // but hey, maybe it's so old it doesn't have that either
  Number.MAX_SAFE_INTEGER = 9007199254740991;
}
var i = 0;
console.log(i, 1/i, 1/i > 0); // 0, Infinity, true
i++;
console.log(i, 1/i, 1/i > 0); // 1, 1, true
// ...eventually i is incremented all the way to Number.MAX_SAFE_INTEGER
i = Number.MAX_SAFE_INTEGER;
console.log(i, 1/i, 1/i > 0); // 9007199254740991 1.1102230246251568e-16, true
i++;
console.log(i, 1/i, 1/i > 0); // 9007199254740992 1.1102230246251565e-16, true
i++;
console.log(i, 1/i, 1/i > 0); // 9007199254740992 1.1102230246251565e-16, true (no change)
console.log(i == i + 1);      // true


79

Risposta:

La condizione 1/i > 0restituirà sempre true:

  • Inizialmente è vero perché 1/0restituisce Infinityed Infinity > 0è vero

  • Rimane vero poiché 1/i > 0è vero per tutti i < Infinitye i++non arriva mai Infinity.

Perché non i++arriva mai Infinity? A causa della limitata precisione del Numbertipo di dati, esiste un valore per il quale i + 1 == i:

9007199254740992 + 1 == 9007199254740992 // true

Una volta iraggiunto quel valore (che corrisponde a ), rimarrà lo stesso anche dopo .Number.MAX_SAFE_INTEGER + 1i++

Abbiamo quindi un ciclo infinito.


Appendice:

Perché lo è 9007199254740992 + 1 == 9007199254740992?

Il Numbertipo di dati di JavaScript è in realtà un float a doppia precisione IEEE 754 a 64 bit . Ciascuno Numberviene smontato e memorizzato in tre parti: segno a 1 bit, esponente a 11 bit e mantissa a 52 bit. Il suo valore è -1 segno × mantissa × 2 esponente .

Come viene rappresentato 9007199254740992 ? Come 1.0 × 2 53 , o in binario:

inserisci qui la descrizione dell'immagine

Incrementando il bit meno significativo della mantissa, otteniamo il numero successivo più alto:

inserisci qui la descrizione dell'immagine

Il valore di quel numero è 1.00000000000000022… × 2 53 = 9007199254740994

Cosa significa? Numberpuò essere 900719925474099 2 o 900719925474099 4 , ma niente in mezzo.

Ora, quale sceglieremo per rappresentare 900719925474099 2 + 1 ? Le regole di arrotondamento IEEE 754 danno la risposta: 900719925474099 2 .


9
breve e corretta, migliore dell'attuale risposta accettata
AlexWien

@AlexWien La risposta accettata è una risposta accettata dal wiki della comunità.
fulvio

2
Non conosco il termine "community wiki accpeted" risposta. Cosa ha a che fare con stackoverflow? Se si tratta di un collegamento esterno, è necessario fornire un collegamento. Le risposte accettate su stackoverflow possono sempre cambiare, lo stato accolto non è definitivo.
AlexWien

"Perché i ++ non raggiunge mai l'Infinito? A causa della limitata precisione del tipo di dati Numero ..." <- Sicuramente non raggiungerebbe mai l'infinito, anche con un tipo di numero a precisione infinita .. Sai, perché non puoi contare fino a infinity: P
Blorgbeard uscirà il

1
@Blorgbeard È possibile contare fino a Infinity con i doppi di precisione limitati, devi solo incremento da un numero molto più grande di 1, ad esempio for (var i = 0; i < Infinity; i += 1E306);. Ma capisco da dove vieni;)
le_m

27

La Number.MAX_SAFE_INTEGERcostante rappresenta il numero intero sicuro massimo in JavaScript. La MAX_SAFE_INTEGERcostante ha un valore di 9007199254740991. Il ragionamento alla base di questo numero è che JavaScript utilizza numeri in formato virgola mobile a precisione doppia come specificato in IEEE 754 e può rappresentare in modo sicuro solo numeri compresi tra - (2 53 - 1) e 2 53 - 1.

Sicuro in questo contesto si riferisce alla capacità di rappresentare esattamente gli interi e di confrontarli correttamente. Ad esempio, Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2valuterà a true, che è matematicamente errato. Vedere Number.isSafeInteger()per ulteriori informazioni.

Poiché MAX_SAFE_INTEGERè una proprietà statica di Number, la usi sempre come Number.MAX_SAFE_INTEGER, piuttosto che come proprietà di un Numberoggetto che hai creato.

AGGIORNARE:

Qualcuno in una risposta che è stata cancellata ha detto: inon raggiungerà mai l'infinito. Una volta raggiunto Number.MAX_SAFE_INTEGER, i++non incrementa più la variabile. Questo in effetti non è corretto.

@TJ Crowder commenta che i = Number.MAX_SAFE_INTEGER; i++; i == Number.MAX_SAFE_INTEGER;è false. Ma l'iterazione successiva raggiunge uno stato immutabile, quindi la risposta principale è corretta.

inell'esempio non arriva mai Infinity.


2
In particolare, 9007199254740992 + 1è 9007199254740992.
Kobi

1
@GerardoFurtado immagino che lo sarebbe.
fulvio

1
@GerardoFurtado for (var i=0; NaN > 0; i++) { console.log(i); }non produrrà nulla.
fulvio

2
@GerardoFurtado: In tal caso, il ciclo si fermerebbe. Il corpo del ciclo non verrebbe mai inserito, poiché il primo test ( 1/i > 0) sarebbe falso, poiché se iè 0, 1/iè NaNed NaN > 0è falso.
TJ Crowder

1
@TJCrowder ho aggiornato la mia risposta. Grazie per la segnalazione!
fulvio
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.