Topik ini membahas pembangkitan bilangan acak di C++ menggunakan pustaka <random>, mulai dari batasan fungsi rand() yang lama, peran engine sebagai sumber deret deterministik, sampai distribusi yang memetakan deret mentah ke rentang yang dibutuhkan. Contoh yang digunakan adalah lemparan dadu dan pembacaan suhu sensor untuk simulasi sederhana. Setelah menyelesaikan topik ini, pembaca dapat memilih engine dan distribusi yang tepat serta mengetahui alasan pola lama mulai ditinggalkan.
Batasan rand() yang lama#
Fungsi rand() dari header <cstdlib> adalah warisan C yang menghasilkan bilangan bulat dari satu keadaan global. Fungsi ini menjanjikan rentang dari 0 sampai RAND_MAX, dan standar hanya menjamin RAND_MAX bernilai minimal 32767. Satu keadaan global juga membuat pemanggilan dari beberapa thread saling mengganggu, dan kualitas deretnya tidak pernah ditetapkan sehingga hasilnya berbeda antarimplementasi.
Program berikut memperlihatkan pola pemakaian yang masih banyak dijumpai pada kode lama.
#include <cstdlib>
#include <iostream>
int main() {
// pola lama: satu keadaan global, seed tunggal, deret int tunggal
std::srand(42);
std::cout << "RAND_MAX = " << RAND_MAX << "\n";
std::cout << "Lima nilai: ";
for (int i = 0; i < 5; ++i) {
std::cout << std::rand() << " ";
}
std::cout << "\n";
return 0;
}RAND_MAX = 2147483647
Lima nilai: 71876166 708592740 1483128881 907283241 442951012Nilai yang dihasilkan berupa bilangan bulat besar yang perlu dipangkas manual untuk mendapatkan rentang kecil, misalnya rand() % 6 untuk dadu. Pemangkasan dengan operator % menimbulkan bias modulo, yaitu beberapa angka muncul lebih sering apabila RAND_MAX tidak habis dibagi enam. Selain itu, deret yang dihasilkan tidak ditetapkan standar, sehingga program yang bergantung pada nilainya tidak dapat direproduksi lintas platform. Pada program dengan beberapa thread, satu keadaan global tersebut juga menjadi titik persaingan, karena seluruh thread memanggil fungsi yang sama tanpa jaminan sinkronisasi apa pun.
Engine: sumber deret deterministik#
Pustaka <random> memisahkan masalah menjadi dua lapis. Lapis pertama adalah engine, yaitu objek yang menghasilkan deret bilangan bulat mentah secara deterministik dari sebuah nilai seed. Seed adalah nilai awal yang menentukan isi seluruh deretnya. Engine paling sering digunakan adalah std::mt19937, mesin Mersenne Twister 32-bit yang deretnya ditetapkan standar secara rinci. Karena penetapan itu, dua program dengan seed yang sama menghasilkan deret yang sama persis di platform mana pun. Pemanggilan fungsi anggota () pada engine melangkah ke bilangan berikutnya, sedangkan pembuatan ulang engine dari seed yang sama mengembalikan seluruh deretnya ke titik awal.
Pilihan seed menentukan apakah hasil dapat direproduksi. Seed tetap seperti angka 42 dipakai saat pengembangan dan pengujian karena keluarannya dapat dibandingkan dari hari ke hari. Untuk program sungguhan yang membutuhkan seed berbeda setiap kali dijalankan, gunakan std::random_device, yaitu sumber keacakan dari sistem operasi, sebagai nilai seed engine.
Dari sisi kualitas, std::mt19937 menyimpan keadaan internal sebesar 624 bilangan 32-bit dan memiliki periode sepanjang , jauh melampaui kebutuhan simulasi biasa. Periode yang panjang berarti deretnya tidak kembali ke titik awal dalam masa pakai program mana pun, dan kualitas sebarannya lebih teruji daripada rand() yang tak pernah ditetapkan standarnya. Engine ini juga tidak memakai keadaan global, sehingga dua engine dengan seed berbeda dapat dipakai secara berdampingan tanpa saling mengganggu.
Distribusi: dari bit mentah ke rentang yang dibutuhkan#
Lapis kedua adalah distribusi, yaitu objek yang memetakan keluaran engine ke rentang dan bentuk sebaran yang diinginkan. std::uniform_int_distribution<int> dadu(1, 6) menghasilkan angka 1 sampai 6 dengan kemungkinan yang sama tanpa bias modulo, sedangkan std::uniform_real_distribution<double> menghasilkan bilangan pecahan pada rentang yang ditentukan. Distribusi lain menutup kebutuhan yang lebih khusus, misalnya std::normal_distribution untuk sebaran berbentuk lonceng pada data pengukuran.
Program berikut melempar sepuluh dadu dan membaca lima suhu sensor. Seednya sengaja ditetapkan sebagai angka 42 agar keluaran program dapat direproduksi.
#include <iostream>
#include <random>
int main() {
// seed tetap agar keluaran dapat direproduksi;
// program nyata menggunakan std::random_device untuk seed
std::mt19937 gen(42);
// lemparan sepuluh dadu enam sisi
std::uniform_int_distribution<int> dadu(1, 6);
std::cout << "Dadu: ";
for (int i = 0; i < 10; ++i) {
std::cout << dadu(gen) << " ";
}
std::cout << "\n";
// lima pembacaan suhu sensor antara 24,0 dan 26,0 derajat
std::uniform_real_distribution<double> suhu(24.0, 26.0);
std::cout << "Suhu: ";
for (int i = 0; i < 5; ++i) {
std::cout << suhu(gen) << " ";
}
std::cout << "\n";
return 0;
}Dadu: 3 5 6 2 5 5 4 4 1 3
Suhu: 24.1999 24.9185 24.6674 24.2857 25.3018 Perintah berikut mengompilasi dan menjalankan program contoh di terminal.
# kompilasi acak.cpp dan simpan biner di /tmp
g++ acak.cpp -o /tmp/acak
# jalankan program hasil kompilasi
/tmp/acakDadu: 3 5 6 2 5 5 4 4 1 3
Suhu: 24.1999 24.9185 24.6674 24.2857 25.3018 Deret engine std::mt19937 ditetapkan standar, tetapi pemetaan di dalam distribusi tidak ditetapkan secara rinci, sehingga nilai suhu dapat berbeda pada pustaka standar lain atau versi yang berbeda. Deret dadu tetap sama karena keluaran engine-nya sama, sedangkan format cetaknya mengikuti pemformatan bawaan std::cout. Karena itu, hasil simulasi yang dibandingkan antarplatform sebaiknya dibandingkan melalui statistik ringkasnya, bukan melalui nilai mentah satu per satu.
Sebuah distribusi dapat dipanggil berulang kali dengan engine yang sama, dan setiap panggilan mengonsumsi satu atau beberapa nilai engine untuk menghasilkan satu nilai keluaran. Distribusi lain tersedia untuk kebutuhan yang berbeda: std::bernoulli_distribution untuk kejadian benar-salah, std::normal_distribution untuk sebaran lonceng pada data pengukuran, dan std::discrete_distribution untuk pilihan dengan bobot yang tidak sama. Pemilihan distribusi menentukan sebaran hasil, sedangkan pemilihan engine menentukan kualitas deret mentah di baliknya.
Kesalahan umum#
- Membuat engine di dalam badan perulangan. Setiap iterasi membangun ulang engine dari seed default yang sama, sehingga seluruh iterasi menghasilkan nilai yang seragam dan bukan deret acak. Deklarasikan engine satu kali di luar perulangan, lalu panggil distribusinya setiap iterasi.
- Menanam seed dengan
std::time(0)pada setiap pemanggilan. Dua pemanggilan yang terjadi pada detik yang sama memperoleh seed yang sama, sehingga hasilnya berulang. Tanam seed sekali di awal program, dan gunakanstd::random_deviceapabila seed yang berbeda setiap eksekusi memang diinginkan. - Meneruskan nilai
rand() % nsebagai angka acak yang seragam. Bias modulo membuat beberapa nilai muncul lebih sering daripada yang lain. Gunakanuniform_int_distributionagar setiap nilai pada rentang memperoleh peluang yang sama.
Ringkasan#
rand()memakai satu keadaan global dengan jaminan kualitas yang lemah, sedangkan<random>memisahkan engine dan distribusi menjadi dua lapis yang masing-masing dapat dipilih.std::mt19937menghasilkan deret mentah yang ditetapkan standar, dan seednya berasal daristd::random_devicepada program sungguhan atau dari angka tetap saat pengujian.- Distribusi seperti
uniform_int_distributiondanuniform_real_distributionmemetakan deret mentah ke rentang tujuan tanpa bias modulo.
Referensi#
- Pseudo-random number generation — cppreference
— Ikhtisar engine dan distribusi pustaka
<random>. - std::mt19937 — cppreference — Parameter mesin Mersenne Twister 32-bit dan jaminan deretnya.
- std::uniform_int_distribution — cppreference — Distribusi bilangan bulat seragam pada rentang yang ditentukan.
- Generating random numbers using Mersenne Twister — LearnCpp
— Panduan bertahap pemakaian
std::mt19937beserta distribusinya.