Jadi rand()
adalah generator bilangan pseudo-acak yang memilih bilangan alami antara 0 dan RAND_MAX
, yang merupakan konstanta yang didefinisikan dalam cstdlib
(lihat artikel ini untuk gambaran umum umum tentang rand()
).
Sekarang apa yang terjadi jika Anda ingin menghasilkan angka acak antara katakan 0 dan 2? Demi penjelasan, katakanlah RAND_MAX
adalah 10 dan saya memutuskan untuk menghasilkan angka acak antara 0 dan 2 dengan menelepon rand()%3
. Namun, rand()%3
tidak menghasilkan angka antara 0 dan 2 dengan probabilitas yang sama!
Ketika rand()
mengembalikan 0, 3, 6, atau 9 rand()%3 == 0
,. Oleh karena itu, P (0) = 4/11
Ketika rand()
mengembalikan 1, 4, 7, atau 10 rand()%3 == 1
,. Oleh karena itu, P (1) = 4/11
Ketika rand()
mengembalikan 2, 5, atau 8 rand()%3 == 2
,. Oleh karena itu, P (2) = 3/11
Ini tidak menghasilkan angka antara 0 dan 2 dengan probabilitas yang sama. Tentu saja untuk rentang kecil, ini mungkin bukan masalah terbesar tetapi untuk rentang yang lebih besar ini dapat membuat distribusinya bias, sehingga bias jumlahnya lebih kecil.
Jadi kapan rand()%n
mengembalikan rentang angka dari 0 hingga n-1 dengan probabilitas yang sama? Ketika RAND_MAX%n == n - 1
. Dalam kasus ini, bersama dengan asumsi kami sebelumnya rand()
mengembalikan angka antara 0 dan RAND_MAX
dengan probabilitas yang sama, kelas modulo dari n juga akan terdistribusi secara merata.
Jadi bagaimana kita mengatasi masalah ini? Cara kasar adalah terus menghasilkan angka acak hingga Anda mendapatkan nomor dalam rentang yang Anda inginkan:
int x;
do {
x = rand();
} while (x >= n);
tapi itu tidak efisien untuk nilai rendah n
, karena Anda hanya memiliki n/RAND_MAX
peluang untuk mendapatkan nilai dalam rentang Anda, dan karenanya Anda harus melakukan RAND_MAX/n
panggilan ke rand()
rata-rata.
Pendekatan rumus yang lebih efisien adalah mengambil rentang besar dengan panjang dapat dibagi dengan n
, seperti RAND_MAX - RAND_MAX % n
, terus menghasilkan angka acak hingga Anda mendapatkan nomor yang terletak di kisaran, dan kemudian mengambil modulus:
int x;
do {
x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));
x %= n;
Untuk nilai kecil n
, ini jarang membutuhkan lebih dari satu panggilan rand()
.
Karya dikutip dan bacaan lebih lanjut:
RAND_MAX%n == n - 1
_ adalah(RAND_MAX + 1) % n == 0
. Saat membaca kode, saya cenderung memahami% something == 0
"terbagi rata" lebih mudah daripada cara lain untuk menghitungnya. Tentu saja, jika stdlib C ++ Anda memilikiRAND_MAX
nilai yang samaINT_MAX
,(RAND_MAX + 1)
tentu tidak akan berfungsi; jadi perhitungan Markus tetap merupakan implementasi teraman.Tetap memilih secara acak adalah cara yang baik untuk menghapus bias.
Memperbarui
Kami dapat membuat kode dengan cepat jika kami mencari rentang x yang dapat dibagi oleh
n
.Loop di atas harus sangat cepat, katakanlah 1 iterasi rata-rata.
sumber
rand()
dapat kembali bukan kelipatann
, maka apa pun yang Anda lakukan, Anda pasti akan mendapatkan 'modulo bias', kecuali jika Anda membuang beberapa nilai tersebut. user1413793 menjelaskan hal itu dengan baik (meskipun solusi yang diajukan dalam jawaban itu benar-benar sial).RAND_MAX+1 - (RAND_MAX+1) % n
pekerjaan dengan benar, tapi saya masih berpikir itu harus ditulisRAND_MAX+1 - ((RAND_MAX+1) % n)
untuk kejelasan.RAND_MAX == INT_MAX
(seperti halnya pada kebanyakan sistem) . Lihat komentar kedua saya ke @ user1413793 di atas.@ user1413793 benar tentang masalah ini. Saya tidak akan membahas lebih lanjut, kecuali untuk membuat satu poin: ya, untuk nilai kecil
n
dan nilai besarRAND_MAX
, bias modulo bisa sangat kecil. Tetapi menggunakan pola yang menginduksi bias berarti bahwa Anda harus mempertimbangkan bias setiap kali Anda menghitung angka acak dan memilih pola yang berbeda untuk kasus yang berbeda. Dan jika Anda membuat pilihan yang salah, bug yang diperkenalkannya halus dan hampir tidak mungkin untuk diuji unit. Dibandingkan dengan hanya menggunakan alat yang tepat (sepertiarc4random_uniform
), itu pekerjaan ekstra, bukan kerja lebih sedikit. Melakukan lebih banyak pekerjaan dan mendapatkan solusi yang lebih buruk adalah rekayasa yang buruk, terutama bila melakukannya dengan benar setiap saat itu mudah di sebagian besar platform.Sayangnya, implementasi dari solusi semuanya salah atau kurang efisien dari yang seharusnya. (Setiap solusi memiliki berbagai komentar yang menjelaskan masalah, tetapi tidak ada solusi yang diperbaiki untuk mengatasinya.) Ini mungkin membingungkan pencari jawaban biasa, jadi saya memberikan implementasi yang dikenal baik di sini.
Sekali lagi, solusi terbaik hanya digunakan
arc4random_uniform
pada platform yang menyediakannya, atau solusi jarak yang serupa untuk platform Anda (sepertiRandom.nextInt
di Jawa). Ini akan melakukan hal yang benar tanpa biaya kode untuk Anda. Ini hampir selalu merupakan panggilan yang tepat untuk dilakukan.Jika Anda tidak memilikinya
arc4random_uniform
, maka Anda dapat menggunakan kekuatan opensource untuk melihat bagaimana penerapannya di atas RNG dengan rentang yang lebih luas (ar4random
dalam hal ini, tetapi pendekatan yang serupa juga dapat bekerja di atas RNG lain).Berikut ini adalah implementasi OpenBSD :
Patut dicatat komentar komit terbaru tentang kode ini untuk mereka yang perlu menerapkan hal-hal serupa:
Implementasi Java juga mudah ditemukan (lihat tautan sebelumnya):
sumber
arcfour_random()
benar-benar menggunakan algoritma RC4 nyata dalam implementasinya, output pasti akan memiliki beberapa bias. Semoga penulis perpustakaan Anda telah beralih menggunakan CSPRNG yang lebih baik di belakang antarmuka yang sama. Saya ingat salah satu BSD sekarang benar-benar menggunakan algoritma ChaCha20 untuk diimplementasikanarcfour_random()
. Lebih lanjut tentang bias keluaran RC4 yang menjadikannya tidak berguna untuk keamanan atau aplikasi penting lainnya seperti video poker: blog.cryptographyengineering.com/2013/03/…/dev/random
juga telah menggunakan RC4 pada beberapa platform di masa lalu (Linux menggunakan SHA-1 dalam mode penghitung). Sayangnya halaman manual yang saya temukan melalui pencarian menunjukkan bahwa RC4 masih digunakan pada berbagai platform yang menawarkanarc4random
(meskipun kode sebenarnya mungkin berbeda).-upper_bound % upper_bound == 0
??-upper_bound % upper_bound
memang akan 0 jikaint
lebih lebar dari 32-bit. Seharusnya(u_int32_t)-upper_bound % upper_bound)
(dengan asumsiu_int32_t
BSD-isme foruint32_t
).Definisi
Modulo Bias adalah bias inheren dalam menggunakan modulo aritmatika untuk mengurangi set output ke subset dari set input. Secara umum, ada bias setiap kali pemetaan antara input dan output set tidak terdistribusi secara merata, seperti dalam kasus menggunakan modulo aritmatika ketika ukuran set output bukan merupakan pembagi dari ukuran set input.
Bias ini sangat sulit untuk dihindari dalam komputasi, di mana angka direpresentasikan sebagai string bit: 0s dan 1s. Menemukan sumber acak yang benar-benar acak juga sangat sulit, tetapi berada di luar cakupan diskusi ini. Untuk sisa jawaban ini, asumsikan bahwa ada sumber tak terbatas bit benar-benar acak.
Contoh soal
Mari pertimbangkan untuk mensimulasi die roll (0 hingga 5) menggunakan bit acak ini. Ada 6 kemungkinan, jadi kita perlu bit yang cukup untuk mewakili angka 6, yaitu 3 bit. Sayangnya, 3 bit acak menghasilkan 8 hasil yang mungkin:
Kita dapat mengurangi ukuran hasil yang ditetapkan menjadi tepat 6 dengan mengambil nilai modulo 6, namun ini menyajikan masalah bias modulo :
110
menghasilkan 0, dan111
menghasilkan 1. Dadu ini dimuat.Solusi Potensial
Mendekati 0:
Daripada mengandalkan bit acak, secara teori seseorang bisa menyewa pasukan kecil untuk melempar dadu sepanjang hari dan mencatat hasilnya dalam database, dan kemudian menggunakan setiap hasil hanya sekali. Ini praktis seperti kedengarannya, dan kemungkinan besar tidak akan menghasilkan hasil yang benar-benar acak (pun intended).
Pendekatan 1:
Alih-alih menggunakan modulus, solusi yang naif tetapi benar secara matematis adalah membuang hasil yang menghasilkan
110
dan111
dan hanya coba lagi dengan 3 bit baru. Sayangnya, ini berarti ada kemungkinan 25% pada setiap roll yang diperlukan untuk re-roll, termasuk masing-masing roll-re itu sendiri. Ini jelas tidak praktis untuk semua kecuali penggunaan yang paling sepele.Pendekatan 2:
Gunakan lebih banyak bit: alih-alih 3 bit, gunakan 4. Ini menghasilkan 16 hasil yang mungkin. Tentu saja, bergulir kembali kapan saja hasilnya lebih besar dari 5 membuat segalanya lebih buruk (10/16 = 62,5%) sehingga itu saja tidak akan membantu.
Perhatikan bahwa 2 * 6 = 12 <16, sehingga kita dapat dengan aman mengambil hasil apa pun yang kurang dari 12 dan mengurangi modulo 6 tersebut untuk mendistribusikan hasil secara merata. 4 hasil lainnya harus dibuang, dan kemudian digulung kembali seperti pada pendekatan sebelumnya.
Kedengarannya bagus pada awalnya, tapi mari kita periksa matematika:
Hasil itu sangat disayangkan, tetapi mari kita coba lagi dengan 5 bit:
Perbaikan yang pasti, tetapi tidak cukup baik dalam banyak kasus praktis. Berita baiknya adalah, menambahkan lebih banyak bit tidak akan meningkatkan peluang untuk membuang dan memutar kembali . Ini berlaku tidak hanya untuk dadu, tetapi dalam semua kasus.
Namun seperti yang ditunjukkan , menambahkan 1 bit ekstra mungkin tidak mengubah apa pun. Bahkan jika kita meningkatkan roll kita menjadi 6 bit, probabilitasnya tetap 6,25%.
Ini menimbulkan 2 pertanyaan tambahan:
Solusi Umum
Untungnya jawaban untuk pertanyaan pertama adalah ya. Masalah dengan 6 adalah bahwa 2 ^ x mod 6 membalik antara 2 dan 4 yang kebetulan merupakan kelipatan dari 2 satu sama lain, sehingga untuk genap x> 1,
Jadi 6 adalah pengecualian daripada aturan. Dimungkinkan untuk menemukan moduli yang lebih besar yang menghasilkan kekuatan 2 berurutan dengan cara yang sama, tetapi pada akhirnya ini harus membungkus, dan kemungkinan pembuangan akan berkurang.
Bukti dari konsep
Berikut adalah contoh program yang menggunakan libcrypo OpenSSL untuk memasok byte acak. Saat mengkompilasi, pastikan untuk menautkan ke perpustakaan
-lcrypto
yang tersedia bagi kebanyakan orang.Saya mendorong bermain dengan
MODULUS
danROLLS
menghargai untuk melihat berapa banyak re-roll sebenarnya terjadi di sebagian besar kondisi. Orang yang skeptis juga mungkin ingin menyimpan nilai yang dihitung untuk mengajukan dan memverifikasi distribusi tampak normal.sumber
randomPool = RAND_bytes(...)
line akan selalu menghasilkanrandomPool == 1
akibat pernyataan. Ini selalu menghasilkan discard dan roll ulang. Saya pikir Anda ingin mendeklarasikan pada jalur yang berbeda. Akibatnya, ini menyebabkan RNG kembali dengan1
untuk setiap iterasi.randomPool
akan selalu dievaluasi1
sesuai dengan dokumentasiRAND_bytes()
OpenSSL untuk karena itu akan selalu berhasil berkatRAND_status()
pernyataan itu.Ada dua keluhan biasa dengan penggunaan modulo.
satu berlaku untuk semua generator. Lebih mudah dilihat dalam batasan kasus. Jika generator Anda memiliki RAND_MAX yang 2 (yang tidak sesuai dengan standar C) dan Anda hanya menginginkan 0 atau 1 sebagai nilai, menggunakan modulo akan menghasilkan 0 dua kali lebih sering (ketika generator menghasilkan 0 dan 2) karena akan menghasilkan 1 (ketika generator menghasilkan 1). Perhatikan bahwa ini benar begitu Anda tidak menjatuhkan nilai, apa pun pemetaan yang Anda gunakan dari nilai generator ke yang diinginkan, yang satu akan terjadi dua kali lebih sering daripada yang lain.
beberapa jenis generator memiliki bit kurang signifikan kurang acak daripada yang lain, setidaknya untuk beberapa parameter mereka, tetapi sayangnya parameter tersebut memiliki karakteristik menarik lainnya (seperti telah mampu memiliki RAND_MAX satu kurang dari kekuatan 2). Masalahnya sudah diketahui dan untuk waktu yang lama implementasi perpustakaan mungkin menghindari masalah (misalnya implementasi sampel rand () dalam standar C menggunakan generator jenis ini, tetapi menjatuhkan 16 bit yang kurang signifikan), tetapi beberapa suka mengeluh tentang itu dan Anda mungkin memiliki nasib buruk
Menggunakan sesuatu seperti
untuk menghasilkan angka acak antara 0 dan n akan menghindari kedua masalah (dan itu menghindari overflow dengan RAND_MAX == INT_MAX)
BTW, C ++ 11 memperkenalkan cara standar untuk reduksi dan generator selain rand ().
sumber
Solusi Mark (Solusi yang diterima) Hampir Sempurna.
Namun, ia memiliki peringatan yang membuang 1 set hasil yang valid dalam setiap skenario di mana
RAND_MAX
(RM
) adalah 1 kurang dari kelipatanN
(Di manaN
= Jumlah hasil yang mungkin valid).yaitu, ketika 'jumlah nilai yang dibuang' (
D
) sama denganN
, maka mereka sebenarnya adalah set yang valid (V)
, bukan set yang tidak valid (I
).Apa yang menyebabkan ini pada beberapa titik Mark kehilangan pandangan tentang perbedaan antara
N
danRand_Max
.N
adalah himpunan yang anggotanya valid hanya terdiri dari Bilangan Bulat Positif, karena berisi hitungan tanggapan yang akan valid. (mis .: SetN
={1, 2, 3, ... n }
)Rand_max
Namun adalah himpunan yang (sebagaimana didefinisikan untuk tujuan kami) termasuk sejumlah bilangan bulat non-negatif.Dalam bentuk yang paling umum, apa yang didefinisikan di sini
Rand Max
adalah Himpunan semua hasil yang valid, yang secara teoritis dapat mencakup angka negatif atau nilai-nilai non-numerik.Oleh karena
Rand_Max
itu lebih baik didefinisikan sebagai set "Kemungkinan Tanggapan".Namun
N
beroperasi terhadap penghitungan nilai dalam set tanggapan yang valid, sehingga meskipun seperti yang didefinisikan dalam kasus khusus kami,Rand_Max
akan menjadi nilai yang kurang dari jumlah total yang dikandungnya.Menggunakan Mark's Solution, Nilai Dihapus ketika: X => RM - RM% N
Seperti yang Anda lihat dalam contoh di atas, ketika nilai X (angka acak yang kita dapatkan dari fungsi awal) adalah 252, 253, 254, atau 255 kita akan membuangnya meskipun keempat nilai ini terdiri dari sekumpulan nilai yang dikembalikan. .
IE: Ketika penghitungan nilai Diabaikan (I) = N (Jumlah hasil yang valid) maka seperangkat nilai pengembalian yang valid akan dibuang oleh fungsi asli.
Jika kita menggambarkan perbedaan antara nilai N dan RM sebagai D, yaitu:
Kemudian ketika nilai D menjadi lebih kecil, Persentase roll-ulang yang tidak dibutuhkan karena metode ini meningkat pada setiap multiplikatif alami. (Ketika RAND_MAX TIDAK sama dengan Nomor Perdana, ini menjadi perhatian yang sah)
MISALNYA:
Karena persentase Rerolls yang dibutuhkan meningkat semakin dekat N datang ke RM, ini dapat menjadi perhatian yang valid pada banyak nilai yang berbeda tergantung pada kendala sistem yang menjalankan kode dan nilai-nilai yang dicari.
Untuk meniadakan hal ini, kita dapat membuat amandemen sederhana.
Ini memberikan versi formula yang lebih umum yang menjelaskan keanehan tambahan menggunakan modulus untuk menentukan nilai maksimal Anda.
Contoh menggunakan nilai kecil untuk RAND_MAX yang merupakan multiplikasi dari N.
Versi Asli:
Versi Umum 1:
Selain itu, dalam hal N harus menjadi jumlah nilai dalam RAND_MAX; dalam hal ini, Anda dapat mengatur N = RAND_MAX +1, kecuali RAND_MAX = INT_MAX.
Namun, Anda hanya bisa menggunakan N = 1, dan nilai X apa pun akan diterima, dan masukkan pernyataan IF untuk pengali akhir Anda. Tetapi mungkin Anda memiliki kode yang mungkin memiliki alasan yang valid untuk mengembalikan 1 ketika fungsi dipanggil dengan n = 1 ...
Jadi mungkin lebih baik menggunakan 0, yang biasanya memberikan Div 0 Error, ketika Anda ingin memiliki n = RAND_MAX + 1
Versi Umum 2:
Kedua solusi ini menyelesaikan masalah dengan hasil valid yang tidak perlu dibuang yang akan terjadi ketika RM + 1 adalah produk dari n.
Versi kedua juga mencakup skenario tepi ketika Anda perlu n untuk menyamakan total set nilai yang mungkin terkandung dalam RAND_MAX.
Pendekatan yang dimodifikasi pada keduanya sama dan memungkinkan solusi yang lebih umum untuk kebutuhan menyediakan angka acak yang valid dan meminimalkan nilai yang dibuang.
Untuk mengulangi:
Solusi Umum Dasar yang mencakup contoh tanda:
Solusi Umum yang Diperpanjang yang Memungkinkan satu skenario tambahan RAND_MAX + 1 = n:
Dalam beberapa bahasa (terutama bahasa yang ditafsirkan) melakukan perhitungan operasi perbandingan di luar kondisi sementara dapat menyebabkan hasil yang lebih cepat karena ini adalah perhitungan satu kali tidak peduli berapa banyak percobaan ulang diperlukan. YMMV!
sumber
RAND_MAX%n = n - 1
Dengan
RAND_MAX
nilai3
(pada kenyataannya seharusnya jauh lebih tinggi dari itu tetapi bias masih ada) masuk akal dari perhitungan ini bahwa ada bias:1 % 2 = 1
2 % 2 = 0
3 % 2 = 1
random_between(1, 3) % 2 = more likely a 1
Dalam hal ini, itu
% 2
adalah apa yang tidak boleh Anda lakukan ketika Anda ingin nomor acak antara0
dan1
. Anda bisa mendapatkan angka acak antara0
dan2
dengan melakukan% 3
, karena dalam kasus ini:RAND_MAX
adalah kelipatan3
.Metode lain
Ada jauh lebih sederhana tetapi untuk menambahkan jawaban lain, berikut adalah solusi saya untuk mendapatkan angka acak antara
0
dann - 1
,n
kemungkinan yang sangat berbeda, tanpa bias.>= n
, restart (tidak ada modulo).Benar-benar data acak tidak mudah diperoleh, jadi mengapa menggunakan bit lebih banyak dari yang dibutuhkan.
Di bawah ini adalah contoh dalam Smalltalk, menggunakan cache bit dari generator nomor pseudo-acak. Saya bukan ahli keamanan jadi gunakan dengan risiko Anda sendiri.
sumber
Sebagai jawaban yang diterima menunjukkan, "modulo bias" berakar pada nilai rendah dari
RAND_MAX
. Dia menggunakan nilai yang sangat kecilRAND_MAX
(10) untuk menunjukkan bahwa jika RAND_MAX adalah 10, maka Anda mencoba menghasilkan angka antara 0 dan 2 menggunakan%, hasil berikut akan menghasilkan:Jadi ada 4 output 0 (peluang 4/10) dan hanya 3 output 1 dan 2 (peluang 3/10).
Jadi itu bias. Angka yang lebih rendah memiliki peluang lebih baik untuk keluar.
Tapi itu hanya muncul begitu jelas ketika
RAND_MAX
kecil . Atau lebih khusus, ketika jumlah yang Anda modding lebih besar dibandingkanRAND_MAX
.Solusi yang jauh lebih baik daripada perulangan (yang sangat tidak efisien dan bahkan tidak disarankan) adalah menggunakan PRNG dengan rentang keluaran yang jauh lebih besar. The Mersenne Twister algoritma memiliki output maksimum 4294967295. Dengan demikian melakukan
MersenneTwister::genrand_int32() % 10
semua maksud dan tujuan, akan terdistribusi secara merata dan efek bias modulo akan hilang sama sekali.sumber
MT::genrand_int32()%2
pilih 0 (50 + 2.3e-8)% dari waktu dan 1 (50 - 2.3e-8)% dari waktu. Kecuali jika Anda membangun RGN kasino (yang mungkin akan Anda gunakan dengan rentang RGN yang jauh lebih besar), pengguna mana pun tidak akan melihat 2.3e-8% ekstra waktu. Anda sedang berbicara tentang angka terlalu kecil untuk menjadi masalah di sini.RAND_MAX
nilai tinggi akan mengurangi bias modulo, tetapi tidak menghilangkannya. Kehancuran akan.RAND_MAX
cukup besar dari jumlah yang Anda pilih, jumlah waktu yang Anda butuhkan untuk membuat ulang nomor acak semakin kecil dan tidak akan mempengaruhi efisiensi. Saya katakan terus mengulang, selama Anda menguji terhadap kelipatan terbesarn
daripada hanyan
seperti yang diusulkan oleh jawaban yang diterima.Saya baru saja menulis kode untuk Metode Unlimited Coin Flip Von Neumann, yang secara teoritis harus menghilangkan bias dalam proses pembuatan angka acak. Info lebih lanjut dapat ditemukan di ( http://en.wikipedia.org/wiki/Fair_coin )
sumber
rand() % 100
100 kali. B) jika semua hasilnya berbeda, ambil yang pertama. C) kalau tidak, GOTO A. Ini akan berfungsi, tetapi dengan jumlah iterasi yang diharapkan sekitar 10 ^ 42, Anda harus cukup sabar. Dan abadi.else if(prev==2) prev= x1; else { if(prev!=x1) return prev; prev=2;}