Mengapa int i = 1024 * 1024 * 1024 * 1024 dikompilasi tanpa kesalahan?

152

Batasnya intadalah dari -2147483648 hingga 2147483647.

Jika saya masukan

int i = 2147483648;

maka Eclipse akan meminta garis bawah merah di bawah "2147483648".

Tetapi jika saya melakukan ini:

int i = 1024 * 1024 * 1024 * 1024;

itu akan dikompilasi dengan baik.

public class Test {
    public static void main(String[] args) {        

        int i = 2147483648;                   // error
        int j = 1024 * 1024 * 1024 * 1024;    // no error

    }
}

Mungkin itu pertanyaan mendasar di Jawa, tapi saya tidak tahu mengapa varian kedua tidak menghasilkan kesalahan.

WUJ
sumber
10
Bahkan jika kompiler biasanya akan "memecah" perhitungan menjadi nilai tunggal sebagai optimasi, itu tidak akan melakukannya jika hasilnya akan menjadi melimpah, karena tidak ada optimasi harus mengubah perilaku program.
Hot Licks
1
Dan tidak bisa menafsirkan 2147483648: literal ini tidak masuk akal.
Denys Séguret
1
Dan Java tidak melaporkan kelebihan integer - operasi "gagal" diam-diam.
Hot Licks
5
@ Jacobsrall: C # akan melaporkan ini sebagai cacat terlepas dari apakah check-ness dihidupkan; semua perhitungan yang hanya terdiri dari ekspresi konstan diperiksa secara otomatis kecuali di dalam wilayah yang tidak dicentang.
Eric Lippert
54
Saya mencegah Anda untuk mengajukan pertanyaan "mengapa tidak" di StackOverflow; mereka sulit dijawab. Pertanyaan "mengapa tidak" mengandaikan bahwa dunia jelas harus menjadi cara yang tidak benar, dan bahwa perlu ada alasan yang bagus untuk itu. Asumsi ini hampir tidak pernah valid. Pertanyaan yang lebih tepat adalah "bagian spesifikasi apa yang menjelaskan bagaimana hitung bilangan bulat konstan dihitung?" atau "bagaimana penanganan integer overflow di Jawa?"
Eric Lippert

Jawaban:

233

Tidak ada yang salah dengan pernyataan itu; Anda hanya mengalikan 4 angka dan menugaskannya ke int, kebetulan ada overflow. Ini berbeda dari menetapkan satu literal , yang akan diperiksa batasnya pada waktu kompilasi.

Ini adalah literal di luar batas yang menyebabkan kesalahan, bukan tugas :

System.out.println(2147483648);        // error
System.out.println(2147483647 + 1);    // no error

Sebaliknya longliteral dapat dikompilasi dengan baik:

System.out.println(2147483648L);       // no error

Perhatikan bahwa, pada kenyataannya, hasilnya adalah masih dihitung pada saat kompilasi karena 1024 * 1024 * 1024 * 1024merupakan ekspresi konstan :

int i = 1024 * 1024 * 1024 * 1024;

menjadi:

   0: iconst_0      
   1: istore_1      

Perhatikan bahwa hasilnya ( 0) hanya dimuat dan disimpan, dan tidak ada multiplikasi yang terjadi.


Dari JLS §3.10.1 (terima kasih kepada @ChrisK karena telah menyampaikannya di komentar):

Ini adalah kesalahan waktu kompilasi jika tipe desimal literal intlebih besar dari 2147483648(2 31 ), atau jika desimal literal 2147483648muncul di mana saja selain sebagai operan dari operator minus unary ( §15.15.4 ).

arshajii
sumber
12
Dan untuk perkalian, JLS mengatakan, Jika perkalian bilangan bulat meluap, maka hasilnya adalah bit orde rendah dari produk matematika yang diwakili dalam beberapa format komplemen dua yang cukup besar. Akibatnya, jika terjadi overflow, maka tanda hasil mungkin tidak sama dengan tanda produk matematika dari dua nilai operan.
Chris K
3
Jawaban yang sangat bagus. Beberapa orang tampaknya memiliki kesan bahwa meluap adalah semacam kesalahan atau kegagalan, tetapi sebenarnya tidak.
Wouter Lievens
3
@ iowatiger08 Semantik bahasa diuraikan oleh JLS, yang tidak tergantung pada JVM (jadi tidak masalah JVM mana yang Anda gunakan).
arshajii
4
@WouterLievens, melimpah adalah biasanya merupakan "tidak biasa" kondisi, jika tidak kondisi kesalahan langsung. Ini adalah hasil dari matematika presisi-terbatas, yang kebanyakan orang tidak secara intuitif berharap terjadi ketika mereka melakukan matematika. Dalam beberapa kasus, seperti -1 + 1, itu tidak berbahaya; tetapi untuk 1024^4itu dapat membutakan orang-orang dengan hasil yang sama sekali tidak terduga, jauh dari apa yang mereka harapkan untuk dilihat. Saya pikir setidaknya harus ada peringatan atau catatan untuk pengguna, dan jangan diam-diam mengabaikannya.
Phil Perry
1
@ iowatiger08: Ukuran int diperbaiki; itu tidak tergantung pada JVM. Java bukan C.
Martin Schröder
43

1024 * 1024 * 1024 * 1024dan 2147483648tidak memiliki nilai yang sama di Jawa.

Sebenarnya, 2147483648 BUKAN BAHKAN NILAI (walaupun 2147483648L) di Jawa. Kompiler benar-benar tidak tahu apa itu, atau bagaimana menggunakannya. Jadi itu merengek.

1024 adalah int yang valid di Java, dan valid int dikalikan dengan valid lain int, selalu valid int. Bahkan jika itu bukan nilai yang sama yang Anda harapkan secara intuitif karena perhitungannya akan meluap.

Contoh

Pertimbangkan contoh kode berikut:

public static void main(String[] args) {
    int a = 1024;
    int b = a * a * a * a;
}

Apakah Anda berharap ini menghasilkan kesalahan kompilasi? Itu menjadi sedikit lebih licin sekarang.
Bagaimana jika kita meletakkan satu loop dengan 3 iterasi dan dikalikan dalam loop?

Kompiler diizinkan untuk mengoptimalkan, tetapi tidak dapat mengubah perilaku program saat melakukannya.


Beberapa info tentang bagaimana kasus ini ditangani:

Di Jawa dan banyak bahasa lainnya, integer akan terdiri dari jumlah bit yang tetap. Perhitungan yang tidak sesuai dengan jumlah bit yang diberikan akan melimpah ; perhitungan pada dasarnya dilakukan modulus 2 ^ 32 di Jawa, setelah itu nilai diubah kembali menjadi bilangan bulat yang ditandatangani .

Bahasa atau API lain menggunakan jumlah bit dinamis ( BigIntegerdi Jawa), naikkan pengecualian atau setel nilai ke nilai ajaib seperti bukan-angka-.

Cruncher
sumber
8
Bagi saya, pernyataan Anda, " 2147483648BUKAN BAHKAN NILAI (walaupun 2147483648Lbegitu)," benar-benar menguatkan poin yang coba dibuat oleh @arshajii.
kdbanman
Ah, maaf, ya, itu aku. Saya melewatkan gagasan melimpah / aritmatika modular dalam jawaban Anda. Perhatikan bahwa Anda dapat memutar kembali jika Anda tidak setuju dengan hasil edit saya.
Maarten Bodewes
@owlstead Suntingan Anda faktanya benar. Alasan saya untuk tidak memasukkannya adalah: terlepas dari bagaimana cara 1024 * 1024 * 1024 * 1024penanganannya, saya benar-benar ingin menekankan bahwa itu tidak sama dengan menulis 2147473648. Ada banyak cara (dan Anda telah mendaftarkan beberapa) yang dapat ditangani oleh suatu bahasa. Ini cukup terpisah, dan bermanfaat. Jadi saya akan meninggalkannya. Banyak informasi menjadi semakin diperlukan ketika Anda memiliki jawaban berperingkat tinggi pada pertanyaan populer.
Cruncher
16

Saya tidak tahu mengapa varian kedua tidak menghasilkan kesalahan.

Perilaku yang Anda sarankan - yaitu, produksi pesan diagnostik ketika perhitungan menghasilkan nilai yang lebih besar dari nilai terbesar yang dapat disimpan dalam bilangan bulat - adalah fitur . Agar Anda dapat menggunakan fitur apa pun, fitur tersebut harus dipikirkan, dianggap sebagai ide yang baik, dirancang, ditentukan, diterapkan, diuji, didokumentasikan, dan dikirimkan kepada pengguna.

Untuk Java, satu atau lebih hal dalam daftar itu tidak terjadi, dan karena itu Anda tidak memiliki fitur. Saya tidak tahu yang mana; Anda harus bertanya kepada desainer Java.

Untuk C #, semua hal itu memang terjadi - sekitar empat belas tahun yang lalu sekarang - dan program yang sesuai dalam C # telah menghasilkan kesalahan sejak C # 1.0.

Eric Lippert
sumber
45
Ini tidak menambah apa pun yang bermanfaat. Meskipun saya tidak keberatan menikam Java, itu tidak menjawab pertanyaan OPs sama sekali.
Seiyria
29
@Seiyria: Poster aslinya menanyakan "kenapa tidak?" pertanyaan - "mengapa dunia tidak seperti yang saya pikirkan seharusnya?" bukan pertanyaan teknis yang tepat tentang kode aktual , dan karena itu ini adalah pertanyaan buruk untuk StackOverflow. Fakta bahwa jawaban yang benar untuk pertanyaan yang samar dan nonteknis tidak jelas dan nonteknis seharusnya tidak mengejutkan. Saya mendorong poster asli untuk mengajukan pertanyaan yang lebih baik, dan menghindari "mengapa tidak?" pertanyaan.
Eric Lippert
18
@ Seiyria: Jawaban yang diterima yang saya perhatikan juga tidak menjawab pertanyaan yang tidak jelas dan tidak teknis ini; pertanyaannya adalah "mengapa ini bukan kesalahan?" dan jawaban yang diterima adalah "karena itu sah". Ini hanya mengulangi pertanyaan ; menjawab "mengapa langit tidak hijau?" dengan "karena biru" tidak menjawab pertanyaan. Tetapi karena pertanyaan itu adalah pertanyaan yang buruk, saya sama sekali tidak menyalahkan penjawabnya; jawabannya adalah jawaban yang masuk akal untuk pertanyaan yang buruk.
Eric Lippert
13
Tn. Eric, Ini adalah pertanyaan yang saya posting: "Mengapa int i = 1024 * 1024 * 1024 * 1024; tanpa laporan kesalahan dalam gerhana?". dan jawaban arshajii adalah apa yang saya apa (mungkin lebih). Terkadang saya tidak bisa mengungkapkan pertanyaan dengan cara yang sangat akurat. Saya pikir itu sebabnya ada beberapa orang memodifikasi beberapa pertanyaan yang diposting lebih akurat di Stackoverflow. Saya pikir jika saya ingin mendapatkan jawaban "karena ini sah", saya tidak akan memposting pertanyaan ini. Saya akan mencoba yang terbaik untuk mengirim beberapa "pertanyaan biasa", tapi tolong pahami seseorang seperti saya yang seorang pelajar dan tidak begitu profesional. Terima kasih.
WUJ
5
@ WUJ Jawaban ini IMHO memberikan wawasan dan perspektif tambahan. Setelah membaca semua jawaban, saya menemukan jawaban ini untuk memberikan validitas sebanyak jawaban yang diberikan. Juga meningkatkan kesadaran bahwa pengembang bukan satu-satunya pelaksana beberapa produk perangkat lunak.
SoftwareCarpenter
12

Selain jawaban arshaji, saya ingin menunjukkan satu hal lagi:

Bukan penugasan yang menyebabkan kesalahan tetapi hanya penggunaan literal . Ketika Anda mencoba

long i = 2147483648;

Anda akan melihatnya juga menyebabkan kesalahan kompilasi karena sisi kanan masih int-literal dan di luar jangkauan.

Jadi operasi dengan int-nilai (dan itu termasuk tugas) dapat meluap tanpa kesalahan kompilasi (dan tanpa kesalahan runtime juga), tetapi kompilator tidak bisa menangani literal yang terlalu besar.

piet.t
sumber
1
Baik. Menetapkan int ke panjang termasuk pemeran implisit. Tapi nilainya tidak pernah ada sebagai int di tempat pertama untuk dicasting :)
Cruncher
4

A: Karena itu bukan kesalahan.

Latar Belakang: Perkalian 1024 * 1024 * 1024 * 1024akan menyebabkan melimpah. Overflow seringkali merupakan bug. Bahasa pemrograman yang berbeda menghasilkan perilaku yang berbeda ketika terjadi luapan. Misalnya, C dan C ++ menyebutnya "perilaku tidak terdefinisi" untuk bilangan bulat yang ditandatangani, dan perilaku tersebut didefinisikan sebagai bilangan bulat tak bertanda (ambil hasil matematika, tambahkan UINT_MAX + 1asalkan hasilnya negatif, kurangi UINT_MAX + 1asalkan hasilnya lebih besar dari UINT_MAX).

Dalam kasus Java, jika hasil operasi dengan intnilai - nilai tidak dalam rentang yang diizinkan, Java secara konseptual menambah atau mengurangi 2 ^ 32 sampai hasilnya dalam kisaran yang diizinkan. Jadi pernyataan itu sepenuhnya sah dan tidak salah. Itu tidak menghasilkan hasil yang Anda harapkan.

Anda pasti dapat berdebat apakah perilaku ini bermanfaat, dan apakah kompiler harus memberi Anda peringatan. Saya akan mengatakan secara pribadi bahwa peringatan akan sangat berguna, tetapi kesalahan akan salah karena itu adalah hukum Jawa.

gnasher729
sumber