Integer overflow adalah salah satu bug paling terselubung di C++. Signed overflow (mis. INT_MAX + 1) adalah undefined behavior — compiler boleh mengasumsikan tidak terjadi dan mengoptimalkan berdasarkan asumsi tersebut, menghasilkan kode yang aneh. Unsigned overflow well-defined (wrap modulo 2^N) tetapi tetap bisa menjadi bug logika. Untuk DSA kompetitif, overflow sering menjadi penyebab WA (Wrong Answer) pada test case besar. Pada Chapter ini kita akan membedah signed vs unsigned overflow, pola pengecekan sebelum operasi, penggunaan long long dan __int128 untuk angka besar, serta modular arithmetic dengan mulmod.
Aturan Dasar #
| Operasi | Perilaku |
|---|---|
Signed overflow (int, long long) | Undefined behavior — compiler boleh mengasumsikan tidak terjadi |
Unsigned overflow (unsigned int, size_t) | Well-defined: wrap modulo 2^N |
| Shift lebih dari bit width | UB |
| Division/modulo by zero | UB (crash) |
Mekanisme: Mengapa Signed Overflow UB #
Compiler modern memakai asumsi “no signed overflow” untuk optimisasi. Misalnya loop for (int i = 0; i <= N; ++i) diasumsikan tidak tak terhingga — compiler boleh hapus pengecekan. Bila overflow sebenarnya terjadi, hasilnya tidak terdefinisi: bisa wrap, bisa crash, bisa menghasilkan nilai aneh.
Sebaliknya, unsigned arithmetic wrap modulo 2^N secara matematis. 0u - 1 == UINT_MAX, 255 + 1 (unsigned char) == 0. Perilaku konsisten di semua platform.
Kode: Deteksi dan Pencegahan #
#include <iostream>
#include <climits>
#include <cstdint>
#include <string>
#include <algorithm>
// 1) Signed overflow adalah UB — compiler bisa asumsikan tidak terjadi
int naive_mul(int a, int b) {
return a * b; // bila a*b > INT_MAX → UB
}
// 2) Pengecekan sebelum operasi — aman
bool safe_mul(int a, int b, int& out) {
if (a > 0 && b > 0 && a > INT_MAX / b) return false;
if (a > 0 && b < 0 && b < INT_MIN / a) return false;
if (a < 0 && b > 0 && a < INT_MIN / b) return false;
if (a < 0 && b < 0 && a < INT_MAX / b) return false; // neg * neg
// a == 0 atau b == 0 → hasil 0, aman
out = a * b;
return true;
}
// 3) Unsigned overflow — well-defined (wrap modulo 2^N)
void demo_unsigned_wrap() {
unsigned int u = 0;
std::cout << "u = " << u << ", u - 1 = " << (u - 1) << " (wrap to UINT_MAX)\n";
unsigned char c = 255;
c = c + 1; // assignment kembali ke unsigned char — wrap to 0
std::cout << "255 + 1 (unsigned char) = " << (int)c << " (wrap to 0)\n";
}
// 4) Cast ke wider type untuk cek
bool safe_add_ll(long long a, long long b, long long& out) {
if (b > 0 && a > LLONG_MAX - b) return false;
if (b < 0 && a < LLONG_MIN - b) return false;
out = a + b;
return true;
}
// 5) __int128 untuk big number (GCC extension)
void demo_int128() {
__int128 big = (__int128)1 << 100; // 2^100
__int128 two_60 = (__int128)1 << 60;
__int128 prod = big * 1; // tetap aman
// Cetak __int128 manual (tidak ada iostream support)
auto print128 = [](__int128 v) {
if (v == 0) { std::cout << '0'; return; }
bool neg = v < 0;
if (neg) v = -v;
std::string s;
while (v > 0) { s += char('0' + (int)(v % 10)); v /= 10; }
if (neg) s += '-';
std::reverse(s.begin(), s.end());
std::cout << s;
};
std::cout << "2^100 = ";
print128(big);
std::cout << "\n2^60 = ";
print128(two_60);
std::cout << "\nprod = ";
print128(prod);
std::cout << '\n';
}
// 6) Modular arithmetic — pencegahan overflow dengan mod
const long long MOD = 1'000'000'007LL;
long long mulmod(long long a, long long b, long long m) {
// a, b < m < 2^63 → a*b bisa overflow long long
// Solusi 1: __int128
return (long long)((__int128)a * b % m);
}
long long mulmod_safe(long long a, long long b, long long m) {
// Solusi 2: russian peasant — tanpa __int128
a %= m; b %= m;
long long res = 0;
while (b > 0) {
if (b & 1) {
res += a;
if (res >= m) res -= m;
}
a <<= 1;
if (a >= m) a -= m;
b >>= 1;
}
return res;
}
int main() {
// Naive (UB bila overflow)
int x = INT_MAX;
std::cout << "INT_MAX = " << x << '\n';
std::cout << "naive_mul(INT_MAX, 2) = " << naive_mul(x, 2) << " (UB!)\n";
// Safe check
int out;
if (safe_mul(x, 2, out)) {
std::cout << "safe_mul ok: " << out << '\n';
} else {
std::cout << "safe_mul: overflow detected\n";
}
if (safe_mul(3, 4, out)) {
std::cout << "safe_mul(3,4) = " << out << '\n';
}
// Unsigned wrap
demo_unsigned_wrap();
// 64-bit add
long long lo;
if (safe_add_ll(LLONG_MAX, 1, lo)) std::cout << "add ok: " << lo << '\n';
else std::cout << "LLONG_MAX + 1 overflow\n";
// __int128
demo_int128();
// Modular arithmetic
long long a = 1'000'000'000LL, b = 1'000'000'000LL;
std::cout << "mulmod(" << a << ", " << b << ", " << MOD << ") = "
<< mulmod(a, b, MOD) << '\n';
std::cout << "mulmod_safe = " << mulmod_safe(a, b, MOD) << '\n';
// Demo: signed overflow UB (sengaja — pragma untuk sembunyikan warning demo)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Woverflow"
int big_mul = 100000 * 100000; // 10^10 — overflow int!
#pragma GCC diagnostic pop
std::cout << "100000 * 100000 (int) = " << big_mul << " (WRONG!)\n";
long long big_mul_ll = 100000LL * 100000LL;
std::cout << "100000LL * 100000LL = " << big_mul_ll << " (correct)\n";
return 0;
}Output yang Dihasilkan:
INT_MAX = 2147483647
naive_mul(INT_MAX, 2) = -2 (UB!)
safe_mul: overflow detected
safe_mul(3,4) = 12
u = 0, u - 1 = 4294967295 (wrap to UINT_MAX)
255 + 1 (unsigned char) = 0 (wrap to 0)
LLONG_MAX + 1 overflow
2^100 = 1267650600228229401496703205376
2^60 = 1152921504606846976
prod = 1267650600228229401496703205376
mulmod(1000000000, 1000000000, 1000000007) = 49
mulmod_safe = 49
100000 * 100000 (int) = 1410065408 (WRONG!)
100000LL * 100000LL = 10000000000 (correct)Bedah Kode Baris-per-Baris #
naive_mul(INT_MAX, 2): operasiINT_MAX * 2adalah signed overflow → UB. Output-2adalah salah satu kemungkinan (compiler wrap dua’s complement), tetapi tidak terjamin. Bisa jadi crash, hasil lain, atau di-optimasi menjadi apa pun.safe_mul(INT_MAX, 2, out): cek sebelum operasi —a > INT_MAX / byaituINT_MAX > INT_MAX / 2 ≈ 1.07 miliar. KarenaINT_MAXlebih besar, returnfalse. Implementasisafe_mulmemeriksa empat kasus tanda (positif×positif, positif×negatif, negatif×positif, negatif×negatif) — masing-masing dengan batas berbeda.safe_mul(3, 4, out): cek lolos (3 <= INT_MAX/4),out = 12.u - 1denganu = 0(unsigned): well-defined wrap modulo2^32. Hasilnya4294967295 == UINT_MAX. Tidak UB.unsigned char c = 255; c = c + 1;:c + 1dipromosikan keint(256), lalu di-assign keunsigned char— modulo2^8menjadi0. Wrap terdefinisi.safe_add_ll(LLONG_MAX, 1, ...): ceka > LLONG_MAX - b→LLONG_MAX > LLONG_MAX - 1→ true → return false. Overflow dideteksi.__int128 big = (__int128)1 << 100: cast1ke__int128sebelum shift, sehingga shift<< 100valid (128 bit). Tanpa cast,1 << 100padaintadalah UB (shift > 31).- Lambda
print128:__int128tidak didukung iostream — harus cetak manual. Loop modulo 10, simpan digit, lalu reverse. Mendukung nilai negatif. mulmod(1e9, 1e9, 1e9+7):1e9 * 1e9 = 1e18— overflowlong long? Tidak,LLONG_MAX ≈ 9.2e18. Tetapi1e9 * 1e9 + εbisa overflow. Pakai__int128untuk aman: hasil1e18 mod (1e9+7) = 49.mulmod_safe: algoritma russian peasant (binary multiplication).bdi-shift ke kanan,adi-shift ke kiri (dua kali lipat). Bila bitbadalah 1, tambahkanakeres. Semua operasi modulom— tidak ada overflow.100000 * 100000(int):10^10overflowint(max ~2.1 miliar). Hasil1410065408(≈ 10^10 mod 2^32 dalam interpretasi dua’s complement). Bug terselubung.100000LL * 100000LL: literalLLmembuat operasi dilong long. Hasil10^10benar.
Pola Pengecekan Sebelum Operasi #
Untuk a + b (signed): if (b > 0 && a > MAX - b) overflow; if (b < 0 && a < MIN - b) overflow;
Untuk a * b (signed): empat kasus berdasarkan tanda a dan b. Lebih sederhana: cek if (a != 0 && (b > MAX/a || b < MIN/a)).
Untuk a - b: if (b > 0 && a < MIN + b) overflow; if (b < 0 && a > MAX + b) overflow;.
Alternatif: cast ke tipe lebih lebar (long long untuk int, __int128 untuk long long) dan cek rentang. Lebih sederhana tetapi tidak portable untuk tipe terbesar.
__int128 untuk Big Number #
GCC/Clang menyediakan __int128 — 128-bit integer. Range ~-1.7×10^38 hingga +1.7×10^38. Mendukung aritmetika dasar tetapi tidak ada dukungan iostream — harus cetak manual.
Penggunaan umum di DSA: modular multiplication (a*b mod m dengan a, b, m < 2^63), Fibonacci ke-100, perhitungan kombinasi besar.
Ringkasan #
- Signed overflow = UB; unsigned overflow = wrap modulo 2^N.
- Compiler boleh mengoptimasi berasumsi signed overflow tidak terjadi — perilaku tidak terjamin.
- Pola cek sebelum operasi: untuk
+/-/×, periksa rentang hasil sebelum eksekusi. - Selalu tulis literal
LL/ULLuntuk angka besar —1000000LL * 1000000LLbukan1000000 * 1000000. - Untuk modular multiplication, pakai
__int128atau algoritma russian peasant untuk menghindari overflowlong long. __int128(GCC/Clang) untuk angka hingga ~10^38 — butuh cetak manual karena iostream tidak mendukung.- Untuk angka > 10^38, gunakan pustaka bignum (
boost::multiprecision).