Kode contoh ini menggambarkan bahwa std::rand
kasus bualan kultus kargo warisan yang harus membuat alis Anda terangkat setiap kali Anda melihatnya.
Ada beberapa masalah di sini:
Kontrak yang biasanya diasumsikan orang — bahkan jiwa malang yang tidak tahu apa-apa dan tidak akan memikirkannya dengan tepat dalam istilah-istilah ini — adalah rand
sampel dari distribusi seragam pada bilangan bulat di 0, 1, 2,… RAND_MAX
,, dan setiap panggilan menghasilkan sampel independen .
Masalah pertama adalah bahwa kontrak yang diasumsikan, sampel acak seragam independen di setiap panggilan, sebenarnya tidak seperti yang dikatakan dokumentasi — dan dalam praktiknya, implementasi secara historis gagal untuk memberikan simulacrum kemandirian yang paling sederhana sekalipun. Misalnya, C99 §7.20.2.1 'The rand
function' mengatakan, tanpa elaborasi:
The rand
fungsi menghitung urutan bilangan bulat pseudo-random dalam kisaran 0 RAND_MAX
.
Ini adalah kalimat yang tidak berarti, karena pseudorandomness adalah properti dari suatu fungsi (atau kelompok fungsi ), bukan dari bilangan bulat, tetapi itu tidak menghentikan birokrat ISO untuk menyalahgunakan bahasa tersebut. Toh, satu-satunya pembaca yang akan kecewa dengan itu tahu lebih baik daripada membaca dokumentasi rand
karena takut sel otak mereka membusuk.
Implementasi historis yang khas di C bekerja seperti ini:
static unsigned int seed = 1;
static void
srand(unsigned int s)
{
seed = s;
}
static unsigned int
rand(void)
{
seed = (seed*1103515245 + 12345) % ((unsigned long)RAND_MAX + 1);
return (int)seed;
}
Ini memiliki sifat yang tidak menguntungkan bahwa meskipun satu sampel dapat didistribusikan secara seragam di bawah benih acak yang seragam (yang bergantung pada nilai spesifik RAND_MAX
), itu bergantian antara bilangan bulat genap dan ganjil dalam panggilan berturut-turut — setelah
int a = rand();
int b = rand();
ekspresi tersebut (a & 1) ^ (b & 1)
menghasilkan 1 dengan probabilitas 100%, yang tidak berlaku untuk sampel acak independen pada distribusi apa pun yang didukung pada bilangan bulat genap dan ganjil. Dengan demikian, sebuah kultus kargo muncul bahwa seseorang harus membuang bit orde rendah untuk mengejar binatang buas yang sulit dipahami dengan 'keacakan yang lebih baik'. (Peringatan spoiler: Ini bukan istilah teknis. Ini adalah tanda bahwa prosa siapa pun yang Anda baca tidak tahu apa yang mereka bicarakan, atau berpikir Anda tidak mengerti dan harus direndahkan.)
Masalah kedua adalah bahwa bahkan jika setiap panggilan melakukan sampel secara independen dari distribusi acak seragam pada 0, 1, 2,…,, RAND_MAX
hasil rand() % 6
tidak akan didistribusikan secara seragam dalam 0, 1, 2, 3, 4, 5 seperti dadu roll, kecuali RAND_MAX
kongruen dengan -1 modulo 6. Counterexample sederhana: Jika RAND_MAX
= 6, maka dari rand()
, semua hasil memiliki probabilitas yang sama 1/7, tetapi dari rand() % 6
, hasil 0 memiliki probabilitas 2/7 sedangkan semua hasil lainnya memiliki probabilitas 1/7 .
Cara yang benar untuk melakukan ini adalah dengan pengambilan sampel penolakan: menggambar berulang kali sampel acak seragam independen s
dari 0, 1, 2,… RAND_MAX
,, dan menolak (misalnya) hasil 0, 1, 2,…, ((RAND_MAX + 1) % 6) - 1
—jika Anda mendapatkan salah satu dari mereka, mulai lagi; jika tidak, hasil s % 6
.
unsigned int s;
while ((s = rand()) < ((unsigned long)RAND_MAX + 1) % 6)
continue;
return s % 6;
Dengan cara ini, himpunan hasil dari rand()
yang kita terima dibagi rata oleh 6, dan setiap kemungkinan hasil s % 6
diperoleh dengan jumlah hasil yang diterima yang sama rand()
, jadi jika rand()
didistribusikan secara seragam maka begitu juga s
. Tidak ada batasan pada jumlah percobaan, tetapi jumlah yang diharapkan kurang dari 2, dan probabilitas keberhasilan tumbuh secara eksponensial dengan jumlah percobaan.
Pilihan yang hasil-hasil dari rand()
Anda menolak tidaklah penting, asalkan Anda memetakan jumlah yang sama dari mereka untuk setiap bilangan bulat di bawah 6. Kode di cppreference.com membuat yang berbeda pilihan, karena masalah pertama di atas-yang tidak dijamin tentang distribusi atau kemandirian keluaran rand()
, dan dalam praktiknya bit orde rendah menunjukkan pola yang tidak 'terlihat cukup acak' (tidak peduli bahwa keluaran berikutnya adalah fungsi deterministik dari yang sebelumnya).
Latihan untuk pembaca: Buktikan bahwa kode di cppreference.com menghasilkan distribusi yang seragam pada gulungan cetakan jika rand()
menghasilkan distribusi seragam pada 0, 1, 2,… RAND_MAX
,.
Latihan untuk pembaca: Mengapa Anda lebih memilih salah satu atau subkumpulan lainnya untuk ditolak? Perhitungan apa yang diperlukan untuk setiap percobaan dalam dua kasus?
Masalah ketiga adalah bahwa ruang benih sangat kecil sehingga meskipun benih didistribusikan secara seragam, musuh yang dipersenjatai dengan pengetahuan tentang program Anda dan satu hasil tetapi bukan benih dapat dengan mudah memprediksi benih dan hasil selanjutnya, yang membuatnya tampak tidak begitu acak setelah semua. Jadi jangan pernah berpikir untuk menggunakan ini untuk kriptografi.
Anda dapat menggunakan rute rekayasa berlebihan yang mewah dan kelas C ++ 11 std::uniform_int_distribution
dengan perangkat acak yang sesuai dan mesin acak favorit Anda seperti angin puyuh Mersenne yang selalu populer std::mt19937
untuk bermain dadu dengan sepupu Anda yang berusia empat tahun, tetapi bahkan itu pun tidak akan berhasil. cocok untuk menghasilkan materi kunci kriptografik — dan twister Mersenne juga merupakan babi ruang yang mengerikan dengan status multi-kilobyte yang mendatangkan malapetaka pada cache CPU Anda dengan waktu penyiapan yang tidak senonoh, sehingga buruk bahkan untuk, misalnya , simulasi Monte Carlo paralel dengan pohon subkomputasi yang dapat direproduksi; popularitasnya kemungkinan besar muncul terutama dari namanya yang menarik. Tapi Anda bisa menggunakannya untuk mainan dadu yang bergulir seperti contoh ini!
Pendekatan lain adalah dengan menggunakan generator nomor pseudorandom kriptografi sederhana dengan keadaan kecil, seperti PRNG penghapusan kunci cepat sederhana , atau hanya stream cipher seperti AES-CTR atau ChaCha20 jika Anda yakin ( misalnya , dalam simulasi Monte Carlo untuk penelitian dalam ilmu alam) bahwa tidak ada konsekuensi yang merugikan untuk memprediksi hasil masa lalu jika negara pernah dikompromikan.
std::uniform_int_distribution
untuk dadu