Mengapa i = i + i memberi saya 0?

96

Saya memiliki program sederhana:

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

Ketika saya menjalankan program ini, yang saya lihat hanyalah 0untuk ioutput saya. Saya akan mengharapkan putaran pertama kami akan i = 1 + 1, diikuti oleh i = 2 + 2, diikuti oleh i = 4 + 4dll.

Apakah ini karena fakta bahwa segera setelah kami mencoba mendeklarasikan ulang idi sisi kiri, nilainya disetel ulang ke 0?

Jika ada yang bisa menunjukkan saya ke detail yang lebih baik dari ini, itu akan bagus.

Ubah intmenjadi longdan tampaknya mencetak nomor seperti yang diharapkan. Saya terkejut betapa cepatnya mencapai nilai maksimal 32-bit!

DeaIss
sumber

Jawaban:

168

Masalahnya adalah karena kelebihan bilangan bulat.

Dalam aritmatika pelengkap dua 32-bit:

imemang mulai memiliki nilai kekuatan dua, tetapi kemudian perilaku luapan dimulai begitu Anda mencapai 2 30 :

2 30 + 2 30 = -2 31

-2 31 + -2 31 = 0

... dalam intaritmatika, karena pada dasarnya ini adalah mod aritmatika 2 ^ 32.

Louis Wasserman
sumber
28
Bisakah Anda memperluas jawaban Anda sedikit?
DeaIss
17
@oOTesterOo Ini mulai mencetak 2, 4 dll tetapi dengan sangat cepat mencapai nilai maksimum bilangan bulat dan "membungkus" ke angka negatif, setelah mencapai nol tetap di nol selamanya
Richard Tingle
52
Jawaban ini bahkan tidak lengkap (bahkan tidak menyebutkan bahwa nilainya tidak akan ada 0pada beberapa iterasi pertama, tetapi kecepatan keluaran mengaburkan fakta itu dari OP). Mengapa itu diterima?
Balapan Ringan di Orbit
16
Agaknya diterima karena dianggap membantu OP.
Joe
4
@LightnessRacesinOrbit Meskipun tidak secara langsung mengatasi masalah yang diajukan OP dalam pertanyaan mereka, jawabannya memberikan informasi yang cukup sehingga programmer yang baik harus dapat menyimpulkan apa yang sedang terjadi.
Kevin
334

pengantar

Masalahnya adalah integer overflow. Jika meluap, nilai kembali ke nilai minimum dan berlanjut dari sana. Jika tidak mengalir, nilai kembali ke nilai maksimum dan berlanjut dari sana. Gambar di bawah ini adalah Odometer. Saya menggunakan ini untuk menjelaskan overflow. Ini adalah luapan mekanis tetapi masih merupakan contoh yang bagus.

Dalam sebuah Odometer, max digit = 9itu melampaui sarana maksimum 9 + 1, yang membawa dan memberikan 0; Namun tidak ada digit yang lebih tinggi untuk diubah menjadi a 1, sehingga penghitung diatur ulang ke zero. Anda mendapatkan ide - "integer overflows" muncul di benak Anda sekarang.

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Literal desimal terbesar dari tipe int adalah 2147483647 (2 31 -1). Semua literal desimal dari 0 hingga 2147483647 dapat muncul di mana pun literal int dapat muncul, tetapi literal 2147483648 hanya dapat muncul sebagai operan operator negasi unary -.

Jika penjumlahan bilangan bulat meluap, maka hasilnya adalah bit orde rendah dari jumlah matematis seperti yang direpresentasikan dalam beberapa format komplemen dua yang cukup besar. Jika terjadi overflow, maka tanda hasil tidak sama dengan tanda penjumlahan matematis dari kedua nilai operan.

Dengan demikian, 2147483647 + 1melimpah dan membungkus -2147483648. Karenanya int i=2147483647 + 1akan meluap, yang tidak sama dengan 2147483648. Selain itu, Anda mengatakan "selalu mencetak 0". Tidak, karena http://ideone.com/WHrQIW . Di bawah, 8 angka ini menunjukkan titik di mana ia berputar dan meluap. Kemudian mulai mencetak 0s. Juga, jangan heran betapa cepatnya menghitung, mesin-mesin saat ini sangat cepat.

268435456
536870912
1073741824
-2147483648
0
0
0
0

Mengapa integer overflow "membungkus"

PDF asli

Ali Gajani
sumber
17
Saya telah menambahkan animasi untuk "Pacman" untuk tujuan simbolis tetapi juga berfungsi sebagai visual yang bagus tentang bagaimana seseorang akan melihat 'integer overflows'.
Ali Gajani
9
Ini adalah jawaban favorit saya di situs ini sepanjang waktu.
Lee White
2
Anda sepertinya melewatkan bahwa ini adalah urutan penggandaan, bukan menambahkan satu.
Paŭlo Ebermann
2
Saya pikir animasi pacman mendapatkan jawaban ini lebih banyak suara positif daripada jawaban yang diterima. Dapatkan suara positif lain untuk saya - ini salah satu game favorit saya!
Husman
3
Untuk siapa pun yang tidak mendapatkan simbolisme: en.wikipedia.org/wiki/Kill_screen#Pac-Man
wei2912
46

Tidak, ini tidak hanya mencetak angka nol.

Ubah ke ini dan Anda akan melihat apa yang terjadi.

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

Apa yang terjadi disebut overflow.

peter.petrov
sumber
61
Cara yang menarik untuk menulis loop for :)
Bernhard
17
@Bernhard Mungkin untuk menjaga struktur program OP.
Taemyr
4
@Taemyr Mungkin, tapi kemudian dia bisa menggantinya truedengan i<10000:)
Bernhard
7
Saya hanya ingin menambahkan beberapa pernyataan; tanpa menghapus / mengubah pernyataan apa pun. Saya terkejut itu menarik perhatian yang begitu luas.
peter.petrov
18
Anda dapat menggunakan operator tersembunyi yang dalam while(k --> 0)bahasa sehari-hari disebut "saat kpergi ke 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);
        }
    }

out put:

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

when sum > Integer.MAX_INT then assign i = 0;
TrungTran05T3
sumber
4
Um, tidak, ini hanya berfungsi untuk urutan khusus ini sampai nol. Coba mulai dengan 3.
Paŭlo Ebermann
4

Karena saya tidak memiliki cukup reputasi, saya tidak dapat memposting gambar output untuk program yang sama di C dengan output terkontrol, Anda dapat mencoba sendiri dan melihat bahwa itu benar-benar mencetak 32 kali dan kemudian seperti yang dijelaskan karena luapan i = 1073741824 + 1073741824 berubah menjadi -2147483648 dan satu tambahan lagi berada di luar jangkauan int dan berubah menjadi 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;
}
Kaify
sumber
3
Program ini, di C, sebenarnya memicu perilaku tidak terdefinisi di setiap eksekusi, yang memungkinkan kompilator mengganti keseluruhan program dengan apa pun (bahkan system("deltree C:"), karena Anda berada di DOS / Windows). Overflow integer yang ditandatangani adalah perilaku yang tidak ditentukan di C / C ++, tidak seperti Java. Berhati-hatilah saat menggunakan konstruksi semacam ini.
filcab
@filcab: "ganti seluruh program dengan apa pun" apa yang Anda bicarakan. Saya telah menjalankan program ini di Visual studio 2012 dan berjalan dengan baik untuk kedua signed and unsignedbilangan bulat tanpa perilaku yang tidak ditentukan
Kaify
3
@Kaify: Bekerja dengan baik adalah perilaku tak terdefinisi yang benar-benar valid. Bayangkan bagaimanapun bahwa kode tersebut melakukan i += iuntuk 32+ iterasi, lalu miliki if (i > 0). Kompilator dapat mengoptimalkannya if(true)karena jika kita selalu menambahkan bilangan positif, iakan selalu lebih besar dari 0. Ia juga bisa membiarkan kondisi di, di mana ia tidak akan dieksekusi, karena luapan diwakili di sini. Karena kompilator dapat menghasilkan dua program yang sama-sama valid dari kode itu, ini merupakan perilaku yang tidak terdefinisi.
3Doubloons
1
@Kaify: ini bukan analisis leksikal, ini adalah kompilator yang menyusun kode Anda dan, mengikuti standar, mampu melakukan pengoptimalan yang "aneh". Seperti lingkaran yang dibicarakan 3Doubloons. Hanya karena kompiler yang Anda coba sepertinya selalu melakukan sesuatu, itu tidak berarti standar menjamin program Anda akan selalu berjalan dengan cara yang sama. Anda memiliki perilaku tidak terdefinisi, beberapa kode mungkin telah dihilangkan karena tidak ada cara untuk sampai ke sana (UB menjamin itu). Posting ini dari blog llvm (dan tautan di dalamnya) memiliki informasi lebih lanjut: blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
filcab
2
@Kaify: Maaf karena tidak mengatakannya, tapi mengatakan "merahasiakannya" adalah salah, terutama jika itu adalah hasil kedua, di Google, untuk "perilaku tidak terdefinisi", yang merupakan istilah khusus yang saya gunakan untuk apa yang dipicu .
filcab
4

Nilai idisimpan dalam memori menggunakan jumlah digit biner yang tetap. Ketika suatu angka membutuhkan lebih banyak digit daripada yang tersedia, hanya digit terendah yang disimpan (digit tertinggi hilang).

Menjumlahkan idirinya sendiri sama dengan mengalikan idua. Sama seperti mengalikan angka dengan sepuluh dalam notasi desimal dapat dilakukan dengan menggeser setiap digit ke kiri dan meletakkan nol di kanan, mengalikan angka dengan dua dalam notasi biner dapat dilakukan dengan cara yang sama. Ini menambahkan satu digit di kanan, jadi satu digit hilang di sebelah kiri.

Di sini nilai awalnya adalah 1, jadi jika kita menggunakan 8 digit untuk menyimpan i(misalnya),

  • setelah 0 iterasi, nilainya adalah 00000001
  • setelah 1 iterasi, nilainya adalah 00000010
  • setelah 2 iterasi, nilainya adalah 00000100

dan seterusnya, sampai langkah bukan nol terakhir

  • setelah 7 iterasi, nilainya adalah 10000000
  • setelah 8 iterasi, nilainya adalah 00000000

Tidak peduli berapa banyak digit biner yang dialokasikan untuk menyimpan nomor tersebut, dan tidak peduli berapa nilai awalnya, pada akhirnya semua digit akan hilang saat didorong ke kiri. Setelah itu, terus menggandakan angka tidak akan mengubah angka - angka tersebut akan tetap diwakili oleh semua nol.

anak bintang
sumber
3

Benar, tetapi setelah 31 iterasi, 1073741824 + 1073741824 tidak menghitung dengan benar dan setelah itu hanya mencetak 0.

Anda dapat melakukan refactor untuk menggunakan BigInteger, sehingga infinite loop Anda akan berfungsi dengan benar.

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);
        }
    }
}
Bruno Volpato
sumber
Jika saya menggunakan long, bukan int, tampaknya akan mencetak> 0 angka untuk waktu yang lama. Mengapa tidak mengalami masalah ini setelah 63 iterasi?
DeaIss
1
"Tidak menghitung dengan benar" adalah karakterisasi yang salah. Perhitungannya benar sesuai dengan apa yang menurut spesifikasi Java harus terjadi. Masalah sebenarnya adalah bahwa hasil perhitungan (ideal) tidak dapat direpresentasikan sebagai int.
Stephen C
@oOTesterOo - karena longbisa mewakili angka yang lebih besar daripada yang intbisa.
Stephen C
Panjang memiliki jangkauan yang lebih besar. Jenis BigInteger menerima nilai / panjang apa pun yang dapat dialokasikan JVM Anda.
Bruno Volpato
Saya berasumsi int overflows setelah 31 iterasi karena itu adalah angka berukuran maksimal 32-bit, dan selama yang 64-bit akan mencapai maksimumnya setelah 63? Mengapa tidak demikian?
DeaIss
2

Untuk men-debug kasus seperti itu, sebaiknya kurangi jumlah iterasi dalam loop. Gunakan ini sebagai ganti while(true):

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

Anda kemudian dapat melihat bahwa itu dimulai dengan 2 dan menggandakan nilainya hingga menyebabkan luapan.

pengguna3732069
sumber
2

Saya akan menggunakan angka 8-bit untuk ilustrasi karena dapat dirinci sepenuhnya dalam waktu singkat. Bilangan hex dimulai dengan 0x, sedangkan bilangan biner dimulai dengan 0b.

Nilai maksimal untuk bilangan bulat unsigned 8-bit adalah 255 (0xFF atau 0b11111111). Jika Anda menambahkan 1, biasanya Anda akan mendapatkan: 256 (0x100 atau 0b100000000). Tetapi karena itu terlalu banyak bit (9), itu melebihi batas maksimum, jadi bagian pertama akan dibuang, meninggalkan Anda dengan 0 efektif (0x (1) 00 atau 0b (1) 00000000, tetapi dengan 1 dijatuhkan).

Jadi, saat program Anda berjalan, Anda mendapatkan:

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
...
kaya remer
sumber
1

Literal desimal terbesar untuk jenis intini adalah 2147483648 (= 2 31 ). Semua literal desimal dari 0 hingga 2147483647 dapat muncul di mana pun literal int mungkin muncul, tetapi literal 2147483648 hanya dapat muncul sebagai operan operator negasi unary -.

Jika penjumlahan bilangan bulat meluap, maka hasilnya adalah bit orde rendah dari jumlah matematis seperti yang direpresentasikan dalam beberapa format komplemen dua yang cukup besar. Jika terjadi overflow, maka tanda hasil tidak sama dengan tanda penjumlahan matematis dari kedua nilai operan.

Scooba doo
sumber