Tipe int berhenti di 2.147.483.647 — dan Java tidak akan memberi tahu kita ketika angka tersebut terlampaui. Tambah satu unit ke Integer.MAX_VALUE dan nilainya melipat menjadi -2.147.483.648; tidak ada error, tidak ada exception, tidak ada baris log. Perilaku wrap-around ini pernah menghentikan penghitung penayangan video di YouTube ketika video Gangnam Style menembus 2.147.483.647 tontonan — pelayanan tersebut akhirnya bermigrasi ke penghitung 64 bit. Pada Chapter ini kita bedah mekanisme luapan (overflow), lalu membangun pertahanan bertingkat: deteksi dengan Math.addExact, pencegahan dengan long, dan jangkar terakhir BigInteger.

Mekanisme Melipat #

Aritmetika integer Java bekerja dalam aritmetika modulo 2³² untuk int dan modulo 2⁶⁴ untuk long. Ketika hasil operasi keluar dari rentang, JVM menyimpan 32 (atau 64) bit terbawah hasil tersebut — mekanisme yang persis sama dengan pangkasan bit pada Chapter Type Casting, hanya kali ini terjadi di dalam operasi aritmetika biasa tanpa cast yang kita tulis.

Konsekuensinya simetris dan mengejutkan:

  • Integer.MAX_VALUE + 1 melipat ke Integer.MIN_VALUE — dari puncak positif langsung ke dasar negatif.
  • Integer.MIN_VALUE - 1 melipat kembali ke Integer.MAX_VALUE.
  • Integer.MAX_VALUE * 2 menjadi -2, karena bit-bit tinggi yang memuat nilainya terbuang dan hanya tersisa pola biner terbawah.

Luapan yang Tidak Terdengar #

Program berikut memperlihatkan seluruh pola melipat tersebut dalam sekali jalan:

public class LuapanSenyap {
    public static void main(String[] args) {
        System.out.println("MAX_VALUE     : " + Integer.MAX_VALUE);
        System.out.println("MAX_VALUE + 1 : " + (Integer.MAX_VALUE + 1));
        System.out.println("MIN_VALUE - 1 : " + (Integer.MIN_VALUE - 1));
        System.out.println("MAX_VALUE * 2 : " + (Integer.MAX_VALUE * 2));
        System.out.println("MAX_VALUE + MAX_VALUE : " + (Integer.MAX_VALUE + Integer.MAX_VALUE));

        int views = Integer.MAX_VALUE;
        views = views + 1;                 // melipat diam-diam
        System.out.println("views + 1     : " + views);
    }
}

Output yang Dihasilkan:

text
MAX_VALUE     : 2147483647
MAX_VALUE + 1 : -2147483648
MIN_VALUE - 1 : 2147483647
MAX_VALUE * 2 : -2
MAX_VALUE + MAX_VALUE : -2
views + 1     : -2147483648

Perhatikan tiga hal dari keluaran di atas:

  1. Baris 3 (MAX_VALUE + 1): nilai terbesar int ditambah satu menghasilkan nilai terkecil int. Jarak logisnya hanya satu unit, tetapi hasilnya terpaut hampir 4,3 miliar.
  2. Baris 5-6 (MAX_VALUE * 2 dan MAX_VALUE + MAX_VALUE): dua operasi berbeda menghasilkan -2 — pola 32 bit terbawah dari hasil yang sebenarnya jauh lebih besar.
  3. Baris 9-11 (views + 1): skenario dunia nyata — penghitung penayangan yang menyentuh batas lalu melipat menjadi negatif pada baris berikutnya. Aplikasi yang menampilkan angka ini kepada pengguna akan menampilkan nilai yang mustahil tanpa satu pun error di log.

Deteksi dengan Math.addExact #

Class java.lang.Math menyediakan varian operasi yang memeriksa luapan secara eksplisit: addExact, subtractExact, multiplyExact, incrementExact, dan negateExact — masing-masing untuk int dan long. Ketika hasil keluar rentang, method ini melempar ArithmeticException alih-alih melipat diam-diam. Kegagalan menjadi keras, terlihat, dan dapat ditangani.

Program berikut membandingkan deteksi luapan dengan jalur perhitungan yang sehat:

public class DeteksiLuapan {
    public static void main(String[] args) {
        try {
            int hasil = Math.addExact(Integer.MAX_VALUE, 1);
            System.out.println("tidak terjadi luapan: " + hasil);
        } catch (ArithmeticException e) {
            System.out.println("terdeteksi: " + e.getMessage());
        }

        try {
            int hasil = Math.multiplyExact(1_000_000, 3_000);
            System.out.println("tidak terjadi luapan: " + hasil);
        } catch (ArithmeticException e) {
            System.out.println("terdeteksi: " + e.getMessage());
        }

        int penduduk = 8_600_000;                 // int aman
        long total = (long) penduduk * 365;       // naikkan dulu ke long
        System.out.println("penduduk * 365 = " + total);

        try {
            long besar = Long.MAX_VALUE;
            long hasil = Math.addExact(besar, 1);
            System.out.println("tidak terjadi luapan: " + hasil);
        } catch (ArithmeticException e) {
            System.out.println("terdeteksi: " + e.getMessage());
        }
    }
}

Output yang Dihasilkan:

text
terdeteksi: integer overflow
terdeteksi: integer overflow
penduduk * 365 = 3139000000
terdeteksi: long overflow

Mari kita bedah baris-baris kuncinya:

  1. Baris 3-8 (Math.addExact(Integer.MAX_VALUE, 1)): method menghitung, menemukan hasil 2147483648 tidak muat di int, lalu melempar ArithmeticException dengan pesan integer overflow. Blok catch menangkapnya dan mencetak terdeteksi: integer overflow — bandingkan dengan operator + biasa yang membiarkan nilai melipat tanpa suara.
  2. Baris 10-16 (Math.multiplyExact(1_000_000, 3_000)): hasil matematisnya 3 miliar, melampaui rentang int, sehingga exception yang sama terlempar. Perkalian adalah penyumbang luapan paling aktif karena dua faktor yang sama-sama “masih tampak wajar” dapat melampaui batas begitu digabungkan.
  3. Baris 18-20 ((long) penduduk * 365): strategi pencegahan — operand dinaikkan ke long sebelum perkalian dihitung, sehingga hasil 3139000000 disimpan dengan selamat. Tanpa cast di awal, penduduk * 365 akan dihitung sebagai int lebih dulu dan melipat.
  4. Baris 22-28 (Math.addExact(Long.MAX_VALUE, 1)): long punya batas juga. Pesan exception-nya long overflow — varian long dari method *Exact menjaga rentang 64 bit dengan cara yang sama.

Ketika long pun Tidak Cukup #

Untuk angka yang melampaui ±9,2 kuintiliun — kriptografi, kombinatorika, ilmu data berskala besar — pustaka standar menyediakan java.math.BigInteger. Kelas ini menyimpan angka dengan presisi tak terbatas di heap, sehingga tidak pernah melipat:

import java.math.BigInteger;

public class AngkaTakTerbatas {
    public static void main(String[] args) {
        BigInteger sangatBesar = new BigInteger("9223372036854775808"); // Long.MAX_VALUE + 1
        BigInteger dua = sangatBesar.multiply(BigInteger.TWO);
        System.out.println("nilai  : " + sangatBesar);
        System.out.println("kali 2 : " + dua);
    }
}

Output yang Dihasilkan:

text
nilai  : 9223372036854775808
kali 2 : 18446744073709551616

Program di atas memulai dari angka yang sudah satu unit di luar jangkauan long (2⁶³), lalu mengalikannya dengan dua — operasi mustahil di tipe primitive — dan hasil 18446744073709551616 (2⁶⁴) tercetak utuh. Harganya jelas: BigInteger adalah objek dengan alokasi memori dan operasi method (multiply, bukan *), jadi puluhan kali lebih lambat daripada long. Gunakan hanya ketika rentang memang menuntut.

Luapan integer adalah contoh klasik bahwa “kode berjalan tanpa error” belum berarti “kode benar”. Di ranah floating-point, kelas persoalan yang serupa muncul dalam wujud berbeda: bukan nilai yang melipat, melainkan angka desimal yang tidak pernah bisa disimpan secara persis. Chapter Floating-Point Precision membedah mengapa 0.1 + 0.2 != 0.3 dan bagaimana menyiasatinya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

AngkaTakTerbatas.java

Unduh berkas
java
import java.math.BigInteger;

public class AngkaTakTerbatas {
    public static void main(String[] args) {
        BigInteger sangatBesar = new BigInteger("9223372036854775808"); // Long.MAX_VALUE + 1
        BigInteger dua = sangatBesar.multiply(BigInteger.TWO);
        System.out.println("nilai  : " + sangatBesar);
        System.out.println("kali 2 : " + dua);
    }
}

DeteksiLuapan.java

Unduh berkas
java
public class DeteksiLuapan {
    public static void main(String[] args) {
        try {
            int hasil = Math.addExact(Integer.MAX_VALUE, 1);
            System.out.println("tidak terjadi luapan: " + hasil);
        } catch (ArithmeticException e) {
            System.out.println("terdeteksi: " + e.getMessage());
        }

        try {
            int hasil = Math.multiplyExact(1_000_000, 3_000);
            System.out.println("tidak terjadi luapan: " + hasil);
        } catch (ArithmeticException e) {
            System.out.println("terdeteksi: " + e.getMessage());
        }

        int penduduk = 8_600_000;                 // int aman
        long total = (long) penduduk * 365;       // naikkan dulu ke long
        System.out.println("penduduk * 365 = " + total);

        try {
            long besar = Long.MAX_VALUE;
            long hasil = Math.addExact(besar, 1);
            System.out.println("tidak terjadi luapan: " + hasil);
        } catch (ArithmeticException e) {
            System.out.println("terdeteksi: " + e.getMessage());
        }
    }
}

LuapanSenyap.java

Unduh berkas
java
public class LuapanSenyap {
    public static void main(String[] args) {
        System.out.println("MAX_VALUE     : " + Integer.MAX_VALUE);
        System.out.println("MAX_VALUE + 1 : " + (Integer.MAX_VALUE + 1));
        System.out.println("MIN_VALUE - 1 : " + (Integer.MIN_VALUE - 1));
        System.out.println("MAX_VALUE * 2 : " + (Integer.MAX_VALUE * 2));
        System.out.println("MAX_VALUE + MAX_VALUE : " + (Integer.MAX_VALUE + Integer.MAX_VALUE));

        int views = Integer.MAX_VALUE;
        views = views + 1;                 // melipat diam-diam
        System.out.println("views + 1     : " + views);
    }
}