Perché i = i + i mi da 0?


96

Ho un semplice programma:

public class Mathz {
    static int i = 1;
    public static void main(String[] args) {    
        while (true){
            i = i + i;
            System.out.println(i);
        }
    }
}

Quando ho eseguito questo programma, tutto quello che vedo è 0per imia uscita. Mi sarei aspettato la prima volta che avremmo avuto i = 1 + 1, seguito da i = 2 + 2, seguito da i = 4 + 4ecc.

Ciò è dovuto al fatto che non appena proviamo a dichiarare nuovamente isul lato sinistro, il suo valore viene ripristinato a 0?

Se qualcuno può indicarmi i dettagli più fini di questo sarebbe fantastico.

Cambia intin longe sembra che stia stampando i numeri come previsto. Sono sorpreso di quanto velocemente raggiunge il valore massimo di 32 bit!

Risposte:


168

Il problema è dovuto a un numero intero di overflow.

Nell'aritmetica in complemento a due a 32 bit:

iin effetti inizia con la potenza di due valori, ma poi i comportamenti di overflow iniziano una volta che si arriva a 2 30 :

2 30 + 2 30 = -2 31

-2 31 + -2 31 = 0

... in intaritmetica, poiché è essenzialmente aritmetica mod 2 ^ 32.


28
Potresti espandere un po 'la tua risposta?
DeaIss l'

17
@oOTesterOo Inizia a stampare 2, 4 ecc. ma raggiunge molto rapidamente il valore massimo dell'intero e si "avvolge" su numeri negativi, una volta raggiunto lo zero rimane a zero per sempre
Richard Tingle

52
Questa risposta non è nemmeno completa (non menziona nemmeno che il valore non sarà 0nelle prime iterazioni, ma la velocità dell'output sta oscurando questo fatto dall'OP). Perché è accettato?
Gare di leggerezza in orbita

16
Presumibilmente è stato accettato perché considerato utile dal PO.
Joe

4
@LightnessRacesinOrbit Sebbene non affronti direttamente i problemi che l'OP ha posto nella loro domanda, la risposta fornisce informazioni sufficienti che un programmatore decente dovrebbe essere in grado di dedurre cosa sta succedendo.
Kevin

334

introduzione

Il problema è l'overflow di numeri interi. Se trabocca, torna al valore minimo e prosegue da lì. Se va in underflow, torna al valore massimo e prosegue da lì. L'immagine sotto è di un contachilometri. Lo uso per spiegare gli overflow. È un trabocco meccanico ma comunque un buon esempio.

In un contachilometri, il max digit = 9, quindi andando oltre il mezzo massimo 9 + 1, che riporta e dà un 0; Tuttavia non ci sono cifre più alte da cambiare in a 1, quindi il contatore si ripristina zero. Hai avuto l'idea: ora mi vengono in mente "interi overflow".

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

Il valore letterale decimale più grande di tipo int è 2147483647 (2 31 -1). Tutti i letterali decimali compresi tra 0 e 2147483647 possono apparire ovunque possa apparire un letterale int, ma il letterale 2147483648 può apparire solo come operando dell'operatore di negazione unario -.

Se un'addizione di interi trabocca, il risultato sono i bit di ordine inferiore della somma matematica rappresentati in un formato con complemento a due sufficientemente grande. Se si verifica un overflow, il segno del risultato non è lo stesso del segno della somma matematica dei due valori dell'operando.

Quindi, 2147483647 + 1trabocca e si avvolge in -2147483648. Quindi int i=2147483647 + 1sarebbe overflow, che non è uguale a 2147483648. Inoltre, dici "stampa sempre 0". Non lo fa, perché http://ideone.com/WHrQIW . Di seguito, questi 8 numeri mostrano il punto in cui ruota e trabocca. Quindi inizia a stampare gli 0. Inoltre, non stupitevi di quanto velocemente calcola, le macchine di oggi sono veloci.

268435456
536870912
1073741824
-2147483648
0
0
0
0

Perché l'overflow di numeri interi "avvolge"

PDF originale


17
Ho aggiunto l'animazione per "Pacman" per scopi simbolici, ma serve anche come una grande visuale di come si vedrebbero gli "integer overflow".
Ali Gajani

9
Questa è la mia risposta preferita su questo sito in assoluto.
Lee White

2
Sembra che ti sia sfuggito che questa era una sequenza di raddoppio, non di aggiunta.
Paŭlo Ebermann

2
Penso che l'animazione di Pacman abbia ottenuto questa risposta più voti positivi rispetto alla risposta accettata. Dammi un altro voto positivo: è uno dei miei giochi preferiti!
Husman

3
Per chiunque non abbia
capito

46

No, non stampa solo zeri.

Cambialo in questo e vedrai cosa succede.

    int k = 50;
    while (true){
        i = i + i;
        System.out.println(i);
        k--;
        if (k<0) break;
    }

Ciò che accade si chiama overflow.


61
Modo interessante per scrivere un ciclo for :)
Bernhard

17
@Bernhard Probabilmente è per mantenere la struttura del programma di OP.
Taemyr

4
@Taemyr Probabilmente, ma poi avrebbe potuto sostituirlo truecon i<10000:)
Bernhard

7
Volevo solo aggiungere alcune affermazioni; senza cancellare / modificare alcuna dichiarazione. Sono sorpreso che abbia attirato un'attenzione così ampia.
peter.petrov

18
Avresti potuto usare l'operatore nascosto while(k --> 0)chiamato colloquialmente "mentre kva a 0";)
Laurent LA RIZZA

15
static int i = 1;
    public static void main(String[] args) throws InterruptedException {
        while (true){
            i = i + i;
            System.out.println(i);
            Thread.sleep(100);
        }
    }

produzione:

2
4
8
16
32
64
...
1073741824
-2147483648
0
0

when sum > Integer.MAX_INT then assign i = 0;

4
Ehm, no, funziona solo per questa particolare sequenza per arrivare a zero. Prova a iniziare con 3.
Paŭlo Ebermann

4

Dato che non ho abbastanza reputazione, non posso pubblicare l'immagine dell'output per lo stesso programma in C con output controllato, puoi provare tu stesso e vedere che effettivamente stampa 32 volte e poi come spiegato a causa dell'overflow i = 1073741824 + 1073741824 cambia in -2147483648 e un'altra ulteriore aggiunta è fuori dall'intervallo di int e diventa Zero.

#include<stdio.h>
#include<conio.h>

int main()
{
static int i = 1;

    while (true){
        i = i + i;
      printf("\n%d",i);
      _getch();
    }
      return 0;
}

3
Questo programma, in C, attiva effettivamente un comportamento indefinito in ogni esecuzione, il che consente al compilatore di sostituire l'intero programma con qualsiasi cosa (anche system("deltree C:"), dato che sei in DOS / Windows). L'overflow di numeri interi con segno è un comportamento indefinito in C / C ++, a differenza di Java. Stai molto attento quando usi questo tipo di costrutto.
filcab

@filcab: "sostituisci l'intero programma con qualsiasi cosa" di cosa stai parlando. Ho eseguito questo programma su Visual Studio 2012 e funziona perfettamente per entrambi i signed and unsignednumeri interi senza alcun comportamento indefinito
Kaify

3
@ Kaify: lavorare bene è un comportamento indefinito perfettamente valido. Immagina tuttavia che il codice abbia fatto i += iper 32+ iterazioni, quindi lo avesse fatto if (i > 0). Il compilatore potrebbe ottimizzarlo in if(true)quanto se aggiungiamo sempre numeri positivi, isarà sempre maggiore di 0. Potrebbe anche lasciare la condizione in, dove non verrà eseguita, a causa dell'overflow qui rappresentato. Poiché il compilatore potrebbe produrre due programmi ugualmente validi da quel codice, è un comportamento indefinito.
3Doubloons

1
@ Kaify: non è un'analisi lessicale, è il compilatore che compila il tuo codice e, seguendo lo standard, è in grado di fare ottimizzazioni “strane”. Come il ciclo di cui parlava 3Doubloons. Solo perché i compilatori che provi sembrano sempre fare qualcosa, non significa che lo standard garantisca che il tuo programma funzionerà sempre allo stesso modo. Hai avuto un comportamento indefinito, del codice potrebbe essere stato eliminato poiché non c'è modo di arrivarci (UB lo garantisce). Questi post dal blog di llvm (e collegamenti in esso) contengono
filcab

2
@Kaify: Scusa per non averlo messo, ma è completamente sbagliato dire "tenuto segreto", soprattutto quando è il secondo risultato, su Google, per "comportamento indefinito", che era il termine specifico che ho usato per ciò che è stato attivato .
filcab

4

Il valore di i viene archiviato in memoria utilizzando una quantità fissa di cifre binarie. Quando un numero richiede più cifre di quelle disponibili, vengono memorizzate solo le cifre più basse (le cifre più alte vanno perse).

Aggiungere ia se stesso è come moltiplicarei per due. Proprio come moltiplicare un numero per dieci in notazione decimale può essere eseguito facendo scorrere ogni cifra a sinistra e mettendo uno zero a destra, moltiplicando un numero per due in notazione binaria può essere eseguita allo stesso modo. Questo aggiunge una cifra a destra, quindi una cifra si perde a sinistra.

Qui il valore iniziale è 1, quindi se usiamo 8 cifre per memorizzare i(ad esempio),

  • dopo 0 iterazioni, il valore è 00000001
  • dopo 1 iterazione, il valore è 00000010
  • dopo 2 iterazioni, il valore è 00000100

e così via, fino al passaggio finale diverso da zero

  • dopo 7 iterazioni, il valore è 10000000
  • dopo 8 iterazioni, il valore è 00000000

Non importa quante cifre binarie siano allocate per memorizzare il numero, e non importa quale sia il valore iniziale, alla fine tutte le cifre andranno perse mentre vengono spostate a sinistra. Dopo quel punto, continuare a raddoppiare il numero non cambierà il numero: sarà comunque rappresentato da tutti gli zeri.


3

È corretto, ma dopo 31 iterazioni 1073741824 + 1073741824 non viene calcolato correttamente e successivamente viene stampato solo 0.

Puoi eseguire il refactoring per utilizzare BigInteger, quindi il tuo ciclo infinito funzionerà correttamente.

public class Mathz {
    static BigInteger i = new BigInteger("1");

    public static void main(String[] args) {    

        while (true){
            i = i.add(i);
            System.out.println(i);
        }
    }
}

Se uso long invece di int, sembra che stampino numeri> 0 per molto tempo. Perché non si verifica questo problema dopo 63 iterazioni?
DeaIss l'

1
"Non calcola correttamente" è una caratterizzazione errata. Il calcolo è corretto in base a ciò che la specifica Java dice che dovrebbe accadere. Il vero problema è che il risultato del calcolo (ideale) non può essere rappresentato come un file int.
Stephen C,

@oOTesterOo - perché longpuò rappresentare numeri più grandi di quanto intpossa.
Stephen C,

Long ha una gamma più ampia. Il tipo BigInteger accetta qualsiasi valore / lunghezza che la tua JVM può allocare.
Bruno Volpato

Ho pensato che int overflow dopo 31 iterazioni perché è un numero di dimensioni massime di 32 bit, e così a lungo quale è un 64 bit raggiungerebbe il suo massimo dopo 63? Perché non è così?
DeaIss l'

2

Per eseguire il debug di questi casi è bene ridurre il numero di iterazioni nel ciclo. Usa questo invece del tuo while(true):

for(int r = 0; r<100; r++)

Puoi quindi vedere che inizia con 2 e sta raddoppiando il valore finché non causa un overflow.


2

Userò un numero a 8 bit per l'illustrazione perché può essere completamente dettagliato in un breve spazio. I numeri esadecimali iniziano con 0x, mentre i numeri binari iniziano con 0b.

Il valore massimo per un intero senza segno a 8 bit è 255 (0xFF o 0b11111111). Se aggiungi 1, in genere ti aspetteresti di ottenere: 256 (0x100 o 0b100000000). Ma poiché sono troppi bit (9), è oltre il massimo, quindi la prima parte viene semplicemente eliminata, lasciandoti con 0 effettivamente (0x (1) 00 o 0b (1) 00000000, ma con 1 eliminato).

Quindi, quando il tuo programma viene eseguito, ottieni:

1 = 0x01 = 0b1
2 = 0x02 = 0b10
4 = 0x04 = 0b100
8 = 0x08 = 0b1000
16 = 0x10 = 0b10000
32 = 0x20 = 0b100000
64 = 0x40 = 0b1000000
128 = 0x80 = 0b10000000
256 = 0x00 = 0b00000000 (wraps to 0)
0 + 0 = 0 = 0x00 = 0b00000000
0 + 0 = 0 = 0x00 = 0b00000000
0 + 0 = 0 = 0x00 = 0b00000000
...

1

Il valore letterale decimale più grande del tipo intè 2147483648 (= 2 31 ). Tutti i letterali decimali compresi tra 0 e 2147483647 possono apparire ovunque possa apparire un letterale int, ma il letterale 2147483648 può apparire solo come operando dell'operatore di negazione unario -.

Se un'addizione di interi trabocca, il risultato sono i bit di ordine inferiore della somma matematica rappresentati in un formato con complemento a due sufficientemente grande. Se si verifica un overflow, il segno del risultato non è lo stesso del segno della somma matematica dei due valori dell'operando.

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.