Setiap tipe integer yang sudah kita pakai — short, int, long, long long — sebenarnya punya dua wajah: signed yang bisa negatif dan unsigned yang hanya menerima nol ke atas. Secara teori pertukarannya sederhana: rentang negatif dikorbankan untuk mendapatkan rentang positif dua kali lipat. Dalam praktik, pertukaran itu mahal harganya, karena unsigned membawa dua kejutan: perilaku wrap-around saat melampaui batas, dan aturan konversi yang membuat perbandingan campuran menghasilkan jawaban yang bertentangan dengan intuisi. Chapter ini membuktikan keduanya dengan program yang dijalankan sungguhan, lalu ditutup dengan rekomendasi praktis kapan unsigned layak dipakai.

Dua Wajah Integer #

Tipe integer tanpa kata unsigned adalah tipe signed — sebutan untuk tipe yang menyimpan tanda. Untuk int 32 bit, rentangnya kira-kira -2.147.483.648 sampai +2.147.483.647: satu bit dipakai untuk tanda, sisanya untuk nilai. Varian unsigned int memakai seluruh 32 bit untuk nilai, sehingga rentangnya 0 sampai 4.294.967.295. Pola yang sama berlaku untuk short, long, dan long long. Kata signed sendiri boleh ditulis eksplisit (signed int) tetapi jarang, karena menjadi default.

Angka-angka batas ini bisa dilihat sendiri lewat <limits> pada Chapter sizeof dan limits; untuk sekarang, satu angka perlu dihafal: unsigned int 32 bit mencapai puncaknya di 4.294.967.295, dan angka itu akan muncul kembali sebentar lagi dengan cara yang mengejutkan.

Wrap-Around: Mundur dari Nol #

Apa yang terjadi bila unsigned bernilai 0 lalu dikurangi 1? Untuk unsigned, standar C++ memberi jawaban tegas: hasilnya adalah nilai terbesar yang sanggup ditampung tipe itu, sebagaimana odometer mobil yang berputar ke angka awal setelah mencapai maksimum. Perilaku ini disebut wrap-around, dan karena dijamin oleh standar, hasilnya konsisten di semua platform:

cpp
#include <iostream>

int main() {
    int suhu = -5;                   // signed: boleh negatif
    unsigned int stokGudang = 250u;  // unsigned: nol ke atas saja

    // Wrap-around unsigned: perilaku resmi standar (modulo 2^32)
    unsigned int nilai = 0u;
    nilai = nilai - 1u;

    long jarak = 3'000'000'000L;     // melebihi int; suffix L menetapkan long
    unsigned long long totalByte = 18'000'000'000'000'000'000ULL;

    std::cout << "Suhu       : " << suhu << "\n";
    std::cout << "Stok       : " << stokGudang << "\n";
    std::cout << "0u - 1u    : " << nilai << "\n";
    std::cout << "Jarak      : " << jarak << "\n";
    std::cout << "Total byte : " << totalByte << "\n";

    return 0;
}

Output yang Dihasilkan:

text
Suhu       : -5
Stok       : 250
0u - 1u    : 4294967295
Jarak      : 3000000000
Total byte : 18000000000000000000

Dua baris terakhir output membawa pelajaran penting: 0u - 1u menghasilkan 4294967295 — bukan -1, melainkan nilai maksimum unsigned int. Dalam istilah teknis, aritmetika unsigned bekerja modulo 2³², sehingga “melampaui batas bawah” berarti melompat ke puncak rentang. Perlu juga digarisbawahi bahwa perilaku ini hanya dijamin untuk unsigned. Untuk tipe signed, meluap melewati batas (misalnya INT_MAX + 1) adalah undefined behavior: standar tidak menjanjikan apa pun, dan compiler berhak berbuat semaunya. Jangan mengandalkan wrap-around signed di kode mana pun.

Bandingkan pula baris Jarak: nilai 3.000.000.000 melampaui kapasitas int (maksimum ±2,147 miliar), sehingga kita wajib memakai long beserta suffix L. Tanpa itu, program berisiko menyimpan nilai yang terpotong.

Jebakan Mencampur Signed dan Unsigned #

Wrap-around menjadi berbahaya ketika nilai unsigned menyelinap ke dalam perbandingan dengan signed. Aturan standar: ketika int dan unsigned int bertemu dalam satu ekspresi, int dikonversi menjadi unsigned lebih dulu. Akibatnya, -1 berubah menjadi 4.294.967.295 — nilai yang jelas-jelas tidak lebih kecil dari 1. Program berikut memperlihatkan konsekuensinya:

cpp
#include <iomanip>
#include <iostream>

int main() {
    int sisaStok = -1;               // nilai negatif yang sah untuk int
    unsigned int batasMinimal = 1u;

    std::cout << std::boolalpha;
    std::cout << "sisaStok < batasMinimal : " << (sisaStok < batasMinimal) << "\n";

    return 0;
}

Output yang Dihasilkan:

text
sisaStok < batasMinimal : false

Logika manusia menilai -1 memang lebih kecil dari 1, tetapi program menjawab false. Kedua tipe yang berbeda tanda itu memicu peringatan g++ pada saat kompilasi — peringatan yang kali ini sengaja kita biarkan muncul karena justru itulah pelajarannya:

text
demo-campur.cpp: In function ‘int main()’:
demo-campur.cpp:9:60: warning: comparison of integer expressions of different signedness: ‘int’ and ‘unsigned int’ [-Wsign-compare]
    9 |     std::cout << "sisaStok < batasMinimal : " << (sisaStok < batasMinimal) << "\n";
      |                                                   ~~~~~~~~~^~~~~~~~~~~~~~

Mekanisme di baliknya: karena -1 tidak terwakili dalam rentang unsigned, konversinya menghasilkan 4.294.967.295 (pola yang sama dengan wrap-around di bagian sebelumnya), dan 4.294.967.295 tentu tidak lebih kecil dari 1. Bug semacam ini nyata di dunia kerja — perulangan yang seharusnya berhenti justru berputar liar ketika penghitung signed -1 dibandingkan dengan batas unsigned. Compiler kita peduli dan memberi peringatan; perlakukan peringatan -Wsign-compare sebagai error yang harus dibereskan, bukan debu yang dibiarkan.

Suffix Literal dan Rekomendasi Praktis #

Sama seperti L dan LL pada Chapter Integer, literal unsigned punya suffix sendiri: u atau U untuk unsigned int, dan gabungan seperti ul, lu, ull untuk varian yang lebih lebar. Penulisan 250u, 42U, dan 18'000'000'000'000'000'000ULL pada contoh di atas masing-masing menetapkan tipe literalnya secara eksplisit — berguna saat nilai melampaui kapasitas int atau saat kita ingin kecocokan tipe yang persis dengan variabel tujuan.

SuffixTipe hasilContoh
u / Uunsigned int250u
l / Llong3000000000L
ul / ULunsigned long10000000000UL
ll / LLlong long9000000000000000000LL
ull / ULLunsigned long long18000000000000000000ULL

Dengan aturan main antar tipe integer beres, kita punya amunisi untuk satu fitur kenyamanan yang populer di C++ modern: auto, yang menuliskan tipe untuk kita — dan menariknya, deduksinya mengikuti aturan yang sama persis dengan yang baru saja kita pelajari.

Rujukan resmi untuk topik Chapter ini: integer literal dan usual arithmetic conversions di cppreference.