Bilangan acak muncul di mana-mana dalam pemrograman: data uji, simulasi, game, algoritma probabilistik, dan machine learning. Header <random> dari pustaka standar memecah persoalan menjadi dua bagian yang jelas — engine yang menghasilkan deret angka mentah yang seragam, dan distribution yang membentuk deret itu menjadi angka sesuai kebutuhan (dadu 1-6, gaji berdistribusi normal, keputusan ya/tidak). Dua properti yang membuat pasangan ini jauh lebih baik daripada rand() warisan C: kualitasnya tinggi (periode panjang, teruji secara statistik), dan perilakunya bisa dibuat deterministik melalui seed tetap — artinya urutan “acak” yang sama bisa direproduksi persis saat pengujian, sementara kejutan acak tetap ada bagi pemain game. Program teruji di bawah menunjukkan keduanya, ditutup uji distribusi sederhana atas 6000 lemparan dadu.
Dua Bagian: Engine dan Distribution #
| Lapisan | Contoh | Perannya |
|---|---|---|
| Engine | std::mt19937 (Mersenne Twister 32-bit), std::mt19937_64 | memutar deret integer mentah yang terdistribusi seragam; inti sebenarnya adalah deret deterministik dari seed |
| Distribusi diskrit | std::uniform_int_distribution<int> d(1, 6) | memetakan keluaran engine ke rentang integer, adil antar nilai |
| Distribusi kontinu | std::uniform_real_distribution<double> d(0.0, 1.0) | bilangan real dalam rentang tertentu |
| Distribusi dunia nyata | std::normal_distribution<double> d(rata, simpangan), std::bernoulli_distribution d(p) | bentuk sebaran yang mengikuti fenomena nyata |
Engine yang sama bisa melayani banyak distribusi — praktik yang lazim adalah satu engine per alur logika, lalu setiap kebutuhan sebaran punya objek distribution sendiri. Pemisahan ini pula yang membuat kode lebih mudah diuji: ganti engine dengan versi ber-seed tetap, dan seluruh simulasi menjadi dapat direproduksi.
Seed Tetap untuk Pengujian, random_device untuk Entropy #
Konstruktor std::mt19937 gen(42); memulai deret dari titik yang sama setiap kali program dijalankan — inilah yang membuat contoh di chapter ini tercetak sama persis di mesin Anda dan di verifier. Untuk situasi yang benar-benar butuh ketidakpastian (kunci kriptografi, shuffle permainan), seed diambil dari std::random_device — sumber entropy dari sistem operasi yang tidak bisa direproduksi:
std::random_device rd; // entropy nyata; JANGAN dicetak hasilnya
std::mt19937 gen(rd()); // engine dengan seed yang berbeda tiap jalan
Perhatikan pembagian peran itu: random_device hanya menyuplai seed sekali di awal, selebihnya engine yang memutar deret. Mencetak keluaran random_device merusak deterministik-nya contoh, sehingga di course ini sumber entropy hanya dibahas sebagai konsep dan tidak pernah masuk blok output.
Program Lengkap dan Uji Distribusi #
#include <iomanip>
#include <iostream>
#include <map>
#include <random>
int main() {
std::cout << std::fixed << std::setprecision(3);
// 1. Mesin + distribusi: dadu dengan seed tetap
std::mt19937 gen(42); // Mersenne Twister 32-bit
std::uniform_int_distribution<int> dadu(1, 6);
std::cout << "lima lempar seed 42 : ";
for (int i = 0; i < 5; ++i) {
if (i > 0) std::cout << " "; // pemisah, tanpa spasi di akhir
std::cout << dadu(gen);
}
std::cout << "\n";
// 2. Seed sama -> urutan persis sama (dapat direproduksi)
std::mt19937 genUlang(42);
std::cout << "lempar ulang seed 42 : ";
for (int i = 0; i < 5; ++i) {
if (i > 0) std::cout << " ";
std::cout << dadu(genUlang);
}
std::cout << "\n";
// 3. Distribusi normal dari mesin yang sama
std::mt19937 genNormal(42);
std::normal_distribution<double> gauss(0.0, 1.0);
std::cout << "tiga sampel normal : ";
for (int i = 0; i < 3; ++i) {
if (i > 0) std::cout << " ";
std::cout << gauss(genNormal);
}
std::cout << "\n";
// 4. Uji distribusi sederhana: 6000 lemparan, frekuensi tiap muka
std::map<int, int> frek;
for (int i = 0; i < 6000; ++i) {
++frek[dadu(gen)]; // melanjutkan urutan gen dari langkah 1
}
for (auto [muka, n] : frek) {
std::cout << "muka " << muka << " : " << n << " kali ("
<< n / 6000.0 * 100.0 << "%)\n";
}
return 0;
}Output yang Dihasilkan:
lima lempar seed 42 : 3 5 6 2 5
lempar ulang seed 42 : 3 5 6 2 5
tiga sampel normal : -0.550 0.515 0.474
muka 1 : 1030 kali (17.167%)
muka 2 : 927 kali (15.450%)
muka 3 : 998 kali (16.633%)
muka 4 : 1043 kali (17.383%)
muka 5 : 1010 kali (16.833%)
muka 6 : 992 kali (16.533%)Bedah baris-per-baris:
- Baris 10-11 (
std::mt19937 gen(42); ... uniform_int_distribution<int> dadu(1, 6);): dua objek, dua peran.gen(42)menetapkan seed 42 sehingga seluruh deret ke depan terkunci;dadu(1, 6)tidak menyimpan keadaan — ia hanya membentuk angka mentah dari engine menjadi nilai 1 sampai 6. - Baris 13-16 (
for (int i = 0; i < 5; ++i) ... std::cout << dadu(gen)): lima lemparan menghasilkan3 5 6 2 5. Angka ini bukan “kebetulan yang bisa berubah”: seed tetap membuat urutannya pasti di platform ini. Baris 14 menyisipkan spasi hanya di antara angka, bukan setelahnya, agar output rapi. - Baris 20-24 (
std::mt19937 genUlang(42); ...): engine kedua dengan seed yang sama memutar ulang urutan persis — bukti nyata sifat reproducible yang kita butuhkan untuk unit test dan debug: bug acak yang muncul dengan seed tertentu bisa dipanggil ulang kapan saja. - Baris 29-30 (
std::mt19937 genNormal(42); ... normal_distribution<double> gauss(0.0, 1.0);): distribusi normal standar (rata 0, simpangan baku 1) dari seed yang sama. Sampelnya tercetak-0.550,0.515,0.474— berkumpul di sekitar 0 sebagaimana sebaran lonceng yang dijanjikan. - Baris 39-42 (
std::map<int, int> frek; ... ++frek[dadu(gen)];): uji distribusi sederhana — 6000 lemparan dihitung per muka denganstd::map(terurut berdasarkan key, sehingga pencetakan pasti urut 1 sampai 6). Enginegensengaja dipakai lanjutan, bukan dibuat baru, agar tidak mengulang lima nilai pertama. - Baris 43-46 (
for (auto [muka, n] : frek) ...): structured bindings membongkar pasangan key-value; persentase dihitung sebagain / 6000.0 * 100.0. Hasilnya berkumpul di sekitar 16,7% — sedikit meleset dari 100%/6 yang ideal per muka, dan meleset kecil seperti inilah wajahnya noise statistik pada 6000 sampel, bukan cacat engine.
Mengapa Tidak rand() #
Fungsi rand() warisan C mengumpulkan hampir semua kelemahan sekaligus: jangkauannya cuma 0 sampai RAND_MAX yang sering hanya 32767, satu state global dibagi semua kode sehingga pustaka dan program Anda saling menyeret urutan, idiom rand() % 6 + 1 menghasilkan sebaran yang miring (modulo bias) karena 32768 tidak habis dibagi 6, dan srand(time(nullptr)) membuat urutan tidak bisa direproduksi — kebalikan dari yang dibutuhkan pengujian. <random> menyelesaikan semuanya: engine ber-periode panjang, state terenkapsulasi dalam objek, distribusi yang adil tersedia siap pakai, dan seed tetap memberi reproduksibilitas penuh. Untuk kode baru, rand() sebaiknya dianggap warisan yang tinggal dibaca, bukan dipakai.
Mesin dan distribusi menutup pembahasan angka satu dimensi. Sekarang kita naik satu tingkat: angka yang punya dua komponen sekaligus — bagian real dan bagian imajiner — beserta operasi geometrisnya, di Chapter Complex Numbers.
Rujukan resmi untuk topik Chapter ini: Pustaka bilangan acak <random>
dan std::mt19937
di cppreference.