Angka acak di C berasal dari dua fungsi di <stdlib.h>: rand(), yang menghasilkan nilai berikutnya, dan srand(), yang menetapkan titik awalnya. Angka-angka ini tidak benar-benar acak: itu deret pseudorandom, yang dihitung secara deterministik dari sebuah seed. Ini keterbatasan untuk kriptografi, tapi justru fitur untuk pengujian.
rand() dan RAND_MAX
rand() mengembalikan int di suatu tempat antara 0 dan RAND_MAX, termasuk kedua batasnya. RAND_MAX adalah makro yang dijamin bernilai paling sedikit 32767; di Linux dan macOS nilainya 2147483647.
Jalankan dua kali. Angkanya sama persis di kedua kali, dan itu bukan bug.
Memberi seed dengan srand
Tanpa memanggil srand, deretnya berperilaku seolah kamu memanggil srand(1). Seed yang sama, deret yang sama, setiap kali dijalankan. Untuk mendapat angka yang berbeda setiap kali dijalankan, berikan seed dari sesuatu yang berubah, biasanya waktu saat ini:
time(NULL) dari <time.h> mengembalikan jumlah detik sejak awal tahun 1970, jadi setiap kali dijalankan seed-nya berbeda. Cast ke unsigned int membungkam peringatan soal penyempitan time_t.
Ada tiga aturan soal seed, dan orang sering salah di ketiganya:
Beri seed tepat satu kali, di awal main. Memanggil srand sebelum setiap rand() adalah anti-pola klasik: di dalam loop yang selesai dalam waktu kurang dari satu detik, time(NULL) mengembalikan nilai yang sama di setiap iterasi, jadi kamu memberi seed ulang dengan angka yang sama dan rand() mengembalikan nilai pertama yang sama setiap kali. Hasilnya satu kolom berisi angka "acak" yang identik.
Jangan memberi seed ulang untuk "meningkatkan" keacakan. Kualitas generator berasal dari state internalnya yang terus maju; mengatur ulang state itu berarti membuang deretnya.
time(NULL) punya resolusi satu detik. Dua program yang dijalankan di detik yang sama mendapat deret yang sama. Itu tidak masalah untuk game, tapi salah untuk apa pun yang butuh kemandirian antarnilai.
Angka dalam sebuah rentang
Idiom standarnya memakai operator sisa bagi:
rand() % n /* 0 to n-1 */
rand() % n + min /* min to min+n-1 */
Untuk mendapat min sampai max termasuk keduanya, jumlah nilai yang mungkin adalah max - min + 1:
+ 1 itulah tempat kesalahan off-by-one bersarang. rand() % 6 menghasilkan 0 sampai 5, jadi lemparan dadu adalah rand() % 6 + 1. Menulis rand() % 7 + 1 untuk "memasukkan 6" malah memberimu dadu bersisi tujuh.
Catatan jujur soal bias modulo
rand() % n tidak benar-benar seragam kecuali n membagi habis RAND_MAX + 1.
Bayangkan dengan angka kecil. Kalau RAND_MAX bernilai 9, sehingga rand() mengembalikan 0 sampai 9, sepuluh nilai dengan peluang sama, maka rand() % 3 memetakan 0,3,6,9 ke 0; 1,4,7 ke 1; dan 2,5,8 ke 2. Hasil 0 muncul dengan empat cara dari sepuluh, hasil 1 dan 2 masing-masing tiga cara. Nol 33% lebih mungkin muncul.
Kemiringan yang sama ada dengan RAND_MAX yang sebenarnya, hanya jauh lebih kecil: nilai sisanya adalah (RAND_MAX + 1) % n hasil pertama, masing-masing mendapat satu peluang tambahan dari sekitar 2,1 miliar. Untuk lemparan dadu, kartu yang dikocok, atau simulasi, itu tidak bisa diukur: pakai % lalu lanjutkan.
Saat hal itu memang penting, misalnya untuk pekerjaan statistik atau apa pun yang berhubungan dengan keamanan, tolak nilai sisanya, jangan dilipat masuk:
Loop itu membuang rentang kecil nilai yang menyebabkan kemiringan lalu mengambil lagi. Loop ini cepat selesai, karena irisan yang ditolak hanya bagian yang sangat kecil dari keseluruhan.
Untuk keacakan yang benar-benar menyangkut keamanan, rand() adalah alat yang salah sehati-hati apa pun kamu memakainya: pakai arc4random_buf di macOS dan BSD, getrandom() di Linux, atau BCryptGenRandom di Windows.
Double acak
Bagi dengan RAND_MAX supaya jatuh di [0.0, 1.0], lalu skalakan:
Cast pada (double) rand() itu wajib. Tanpa cast, rand() / RAND_MAX adalah pembagian integer dan hampir selalu bernilai 0, lalu 1 dengan peluang satu banding dua miliar saat mengenai nilai maksimum. Bug ini kelihatan seperti "semua double acakku nol". Lihat type casting untuk alasannya.
Deret yang bisa diulang
Seed tetap memberi deret yang identik setiap kali dijalankan, dan itulah yang kamu inginkan untuk pengujian, sesi debugging, atau game dengan kode level yang bisa dibagikan:
Seed 42 menghasilkan lima angka yang sama setiap kali dipakai, di eksekusi ini dan di eksekusi lain dengan library yang sama. Kemampuan untuk diulang itulah alasan simulasi sebaiknya membiarkan seed dipilih: jalankan dengan jam seperti biasa, dan berikan seed tetap saat mereproduksi bug.
Satu catatan: deret untuk seed tertentu tidak portabel. Library C yang berbeda memakai generator yang berbeda, jadi seed 42 di glibc dan seed 42 di Windows memberi angka yang berbeda. Bisa diulang di satu mesin, tidak antarmesin.
Game dadu
Semuanya digabung: seed sekali, fungsi pembantu untuk rentang, dan array yang menghitung hasilnya:
Histogramnya seharusnya memuncak di 7 dan menurun ke arah 2 dan 12: ada enam cara untuk mendapat 7 dan masing-masing hanya satu cara untuk mendapat 2 atau 12. Generator yang menghasilkan distribusi rata di sini berarti rusak.
Dua halaman terkait: halaman library standar memetakan sisa <stdlib.h>, dan fungsi matematika membahas <math.h>, yang akan kamu butuhkan begitu nilai acak dipakai dalam perhitungan sungguhan.
Pertanyaan yang sering diajukan
Bagaimana cara membuat angka acak di C?
Include <stdlib.h>, berikan seed sekali di awal main dengan srand((unsigned) time(NULL)) (yang butuh <time.h>), lalu panggil rand() untuk setiap nilai. rand() mengembalikan int antara 0 dan RAND_MAX, termasuk kedua batasnya.
Bagaimana cara mendapat angka acak di antara dua nilai di C?
Pakai rand() % (max - min + 1) + min. Untuk lemparan dadu antara 1 dan 6, itu rand() % 6 + 1. % n memetakan hasilnya ke 0..n-1, dan menambahkan min menggeser jendelanya. Pastikan saja jumlahnya mencakup kedua ujung, dan itulah gunanya + 1.
Kenapa program C mencetak angka acak yang sama setiap kali dijalankan?
Karena kamu tidak pernah memanggil srand. Tanpa seed, rand() berperilaku seolah diberi seed 1, jadi setiap kali dijalankan deretnya identik. Panggil srand((unsigned) time(NULL)) sekali di awal program. Sekali saja, jangan sebelum setiap pemanggilan rand(), karena itu malah memperburuk keadaan.
Apa itu bias modulo dalam pembuatan angka acak?
rand() % n hanya benar-benar seragam kalau n membagi habis RAND_MAX + 1. Kalau tidak, beberapa nilai pertama muncul satu kali lebih banyak di seluruh rentangnya, sehingga peluangnya sedikit lebih besar. Dengan RAND_MAX sebesar 2147483647 dan n yang kecil, kemiringannya jauh di bawah apa pun yang bisa diperhatikan game atau simulasi, tapi untuk kriptografi atau statistik, pakai loop penolakan atau generator yang layak.