Mengapa rand ()% 6 bias?

109

Saat membaca cara menggunakan std :: rand, saya menemukan kode ini di cppreference.com

int x = 7;
while(x > 6) 
    x = 1 + std::rand()/((RAND_MAX + 1u)/6);  // Note: 1+rand()%6 is biased

Apa yang salah dengan ekspresi di sebelah kanan? Sudah mencobanya dan bekerja dengan sempurna.

yo_
sumber
24
Perhatikan bahwa lebih baik digunakan std::uniform_int_distributionuntuk dadu
Caleth
1
@Caleth Ya, itu hanya untuk memahami mengapa kode ini 'salah' ..
yO_
15
Mengubah "salah" menjadi "bias"
Cubbi
3
rand()sangat buruk dalam implementasi umum, Anda mungkin juga menggunakan xkcd RNG . Jadi salah karena menggunakan rand().
CodesInChaos
3
Saya menulis hal ini (yah, bukan komentar - itu @Cubbi) dan yang ada dalam pikiran saya saat itu adalah penjelasan dari jawaban Pete Becker . (FYI, ini pada dasarnya adalah algoritme yang sama dengan libstdc ++ uniform_int_distribution.)
TC

Jawaban:

136

Ada dua masalah dengan rand() % 6( 1+tidak memengaruhi masalah mana pun).

Pertama, seperti yang ditunjukkan beberapa jawaban, jika bit rendah rand()tidak seragam, hasil operator sisanya juga tidak seragam.

Kedua, jika jumlah nilai berbeda yang dihasilkan rand()bukan merupakan kelipatan 6, maka sisanya akan menghasilkan nilai yang lebih rendah daripada nilai tinggi. Itu benar bahkan jika rand()mengembalikan nilai yang didistribusikan dengan sempurna.

Sebagai contoh ekstrem, anggaplah yang rand()menghasilkan nilai terdistribusi seragam dalam rentang tersebut [0..6]. Jika Anda melihat sisa untuk nilai tersebut, saat rand()mengembalikan nilai dalam rentang [0..5], sisanya menghasilkan hasil yang terdistribusi secara seragam dalam rentang tersebut [0..5]. Ketika rand()mengembalikan 6, rand() % 6mengembalikan 0, sama seperti jika rand()mengembalikan 0. Jadi Anda mendapatkan distribusi dengan dua kali lebih banyak 0 dari nilai lainnya.

Yang kedua adalah masalah sebenarnya dengan rand() % 6.

Cara untuk menghindari masalah itu adalah dengan membuang nilai yang akan menghasilkan duplikat yang tidak seragam. Anda menghitung kelipatan 6 terbesar yang kurang dari atau sama dengan RAND_MAX, dan setiap kali rand()mengembalikan nilai yang lebih besar dari atau sama dengan kelipatan itu Anda menolaknya dan memanggil `rand () lagi, sebanyak yang diperlukan.

Begitu:

int max = 6 * ((RAND_MAX + 1u) / 6)
int value = rand();
while (value >= max)
    value = rand();

Itu adalah implementasi berbeda dari kode yang dipermasalahkan, yang dimaksudkan untuk menunjukkan dengan lebih jelas apa yang sedang terjadi.

Pete Becker
sumber
2
Saya telah berjanji setidaknya satu orang biasa di situs ini untuk menghasilkan makalah tentang ini, tetapi saya pikir pengambilan sampel dan penolakan dapat membuang momen-momen penting; misalnya membesar-besarkan varians.
Batsyeba
30
Saya membuat grafik tentang seberapa besar bias yang ditimbulkan oleh teknik ini jika rand_max adalah 32768, yang ada dalam beberapa implementasi. ericlippert.com/2013/12/16/…
Eric Lippert
2
@Bathsheba: memang benar bahwa beberapa fungsi penolakan dapat menyebabkan hal ini, tetapi penolakan sederhana ini akan mengubah IID seragam menjadi distribusi IID seragam yang berbeda. Tidak ada bit yang terbawa, begitu independen, semua sampel menggunakan penolakan yang sama sehingga identik, dan sepele untuk menunjukkan keseragaman. Dan momen yang lebih tinggi dari variabel acak integral seragam sepenuhnya ditentukan oleh jangkauannya.
MSalters
4
@ MSalters: Kalimat pertama Anda benar untuk generator yang benar , belum tentu benar untuk generator palsu. Ketika saya pensiun, saya akan menulis makalah tentang ini.
Batsyeba
2
@Anony Berpikirlah dalam bentuk dadu. Anda menginginkan angka acak antara 1 dan 3 dan Anda hanya memiliki dadu bersisi 6 standar. Anda bisa mendapatkannya hanya dengan mengurangkan 3 jika Anda menggulung 4-6. Tetapi katakanlah sebaliknya Anda menginginkan angka antara 1 dan 5. Jika Anda mengurangi 5 saat Anda menggulung 6, maka Anda akan mendapatkan angka 1 dua kali lebih banyak daripada angka lainnya. Pada dasarnya itulah yang dilakukan kode cppreference. Hal yang benar untuk dilakukan adalah memutar ulang 6s. Itulah yang dilakukan Pete di sini: bagi dadu sehingga ada jumlah cara yang sama untuk menggulung setiap angka, dan memutar ulang angka apa pun yang tidak cocok dengan divisi genap
Ray
19

Ada kedalaman tersembunyi di sini:

  1. Penggunaan small uin RAND_MAX + 1u. RAND_MAXdidefinisikan sebagai inttipe, dan seringkali yang terbesar int. Perilaku RAND_MAX + 1tidak akan ditentukan dalam contoh seperti Anda akan meluap signedjenis. Penulisan 1umemaksa jenis konversi RAND_MAXmenjadi unsigned, sehingga menghindari luapan.

  2. Penggunaan % 6 can (tetapi pada setiap implementasi yang std::randpernah saya lihat tidak ) menimbulkan bias statistik tambahan di atas dan di luar alternatif yang disajikan. Contoh di mana % 6berbahaya adalah kasus di mana penghasil angka memiliki dataran korelasi dalam bit orde rendah, seperti implementasi IBM yang agak terkenal (dalam C) randpada, saya pikir, tahun 1970-an yang membalik bit tinggi dan rendah sebagai "final berkembang". Pertimbangan lebih lanjut adalah bahwa 6 sangat kecil lih. RAND_MAX, jadi akan ada efek minimal jika RAND_MAXbukan kelipatan 6, yang mungkin juga bukan.

Sebagai kesimpulan, akhir-akhir ini, karena mudah diatur, saya akan menggunakan % 6. Ini tidak mungkin untuk memperkenalkan anomali statistik selain yang diperkenalkan oleh generator itu sendiri. Jika Anda masih ragu, uji generator Anda untuk melihat apakah generator tersebut memiliki properti statistik yang sesuai untuk kasus penggunaan Anda.

Batsyeba
sumber
12
% 6menghasilkan hasil yang bias jika jumlah nilai berbeda yang dihasilkan rand()bukan merupakan kelipatan 6. Prinsip lubang merpati. Memang, biasnya kecil bila RAND_MAXjauh lebih besar dari 6, tapi itu ada. Dan untuk rentang target yang lebih besar, efeknya tentu saja lebih besar.
Pete Becker
2
@ PeteBecker: Memang, saya harus menjelaskannya. Tetapi perhatikan bahwa Anda juga mendapatkan pigeon-holing saat rentang sampel Anda mendekati RAND_MAX, karena efek pemotongan pembagian integer.
Batsyeba
2
@Bathsheba bukankah efek pemotongan menyebabkan hasil yang lebih besar dari 6 dan dengan demikian dalam eksekusi berulang dari seluruh operasi?
Gerhardh
1
@Gerhardh: Benar. Bahkan, itu mengarah tepat ke hasilnya x==7. Secara bsically, Anda membagi rentang [0, RAND_MAX]dalam 7 subrentang, 6 dengan ukuran yang sama dan satu subrentang yang lebih kecil di akhir. Hasil dari subrange terakhir akan dibuang. Cukup jelas bahwa Anda tidak dapat memiliki dua sub-rentang yang lebih kecil pada akhirnya dengan cara ini.
MSalters
@ MSalters: Memang. Tetapi perhatikan bahwa cara lain masih menderita karena pemotongan. Hipotesis saya adalah bahwa orang gemuk untuk yang terakhir karena perangkap statistik lebih sulit untuk dipahami!
Batsyeba
13

Kode contoh ini menggambarkan bahwa std::randkasus 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 randsampel 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 randfunction' mengatakan, tanpa elaborasi:

The randfungsi 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 randkarena 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_MAXhasil rand() % 6tidak akan didistribusikan secara seragam dalam 0, 1, 2, 3, 4, 5 seperti dadu roll, kecuali RAND_MAXkongruen 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 sdari 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 % 6diperoleh 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_distributiondengan perangkat acak yang sesuai dan mesin acak favorit Anda seperti angin puyuh Mersenne yang selalu populer std::mt19937untuk 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.

Ossifrage mual
sumber
4
"waktu penyiapan yang tidak senonoh" Anda seharusnya tidak menggunakan lebih dari satu generator nomor acak (per utas), jadi waktu penyiapan akan diamortisasi kecuali jika program Anda tidak berjalan terlalu lama.
JAB
2
Beri suara negatif BTW karena tidak memahami bahwa loop dalam pertanyaan melakukan pengambilan sampel penolakan yang sama persis, dengan (RAND_MAX + 1 )% 6nilai yang persis sama . Tidak peduli bagaimana Anda membagi hasil yang mungkin. Anda dapat menolaknya dari mana saja dalam kisaran tersebut [0, RAND_MAX), selama ukuran kisaran yang diterima adalah kelipatan 6. Sial, Anda dapat menolak hasil apa pun x>6, dan Anda tidak akan membutuhkannya %6lagi.
MSalters
12
Saya tidak begitu senang dengan jawaban ini. Kata-kata kasar bisa bagus tapi Anda membawanya ke arah yang salah. Misalnya, Anda mengeluh bahwa "keacakan yang lebih baik" bukanlah istilah teknis dan tidak ada artinya. Ini setengah benar. Ya, ini bukan istilah teknis, tetapi ini adalah singkatan yang sangat berarti dalam konteks. Menyindir bahwa pengguna istilah seperti itu tidak tahu apa-apa atau jahat, itu sendiri, salah satunya. "Keacakan yang baik" mungkin sangat sulit untuk didefinisikan secara tepat, tetapi cukup mudah untuk dipahami saat suatu fungsi menghasilkan hasil dengan properti keacakan yang lebih baik atau lebih buruk.
Konrad Rudolph
3
Saya menyukai jawaban ini. Ini adalah sedikit kata-kata kasar, tetapi memiliki banyak informasi latar belakang yang bagus. Ingat, para ahli REAL hanya pernah menggunakan generator acak perangkat keras, masalahnya adalah yang sulit.
Tiger4Hire
10
Bagi saya itu kebalikannya. Meskipun mengandung informasi yang bagus, itu terlalu banyak kata-kata kasar untuk dianggap sebagai opini. Kegunaan disisihkan.
Tuan Lister
2

Saya bukan pengguna C ++ berpengalaman dengan cara apa pun, tetapi tertarik untuk melihat apakah jawaban lain tentang std::rand()/((RAND_MAX + 1u)/6)kurang bias daripada yang 1+std::rand()%6sebenarnya berlaku. Jadi saya menulis program tes untuk mentabulasi hasil untuk kedua metode (saya belum menulis C ++ dalam usia, silakan periksa). Tautan untuk menjalankan kode ditemukan di sini . Ini juga direproduksi sebagai berikut:

// Example program
#include <cstdlib>
#include <iostream>
#include <ctime>
#include <string>

int main()
{
    std::srand(std::time(nullptr)); // use current time as seed for random generator

    // Roll the die 6000000 times using the supposedly unbiased method and keep track of the results

    int results[6] = {0,0,0,0,0,0};

    // roll a 6-sided die 20 times
    for (int n=0; n != 6000000; ++n) {
        int x = 7;
        while(x > 6) 
            x = 1 + std::rand()/((RAND_MAX + 1u)/6);  // Note: 1+rand()%6 is biased

        results[x-1]++;
    }

    for (int n=0; n !=6; n++) {
        std::cout << results[n] << ' ';
    }

    std::cout << "\n";


    // Roll the die 6000000 times using the supposedly biased method and keep track of the results

    int results_bias[6] = {0,0,0,0,0,0};

    // roll a 6-sided die 20 times
    for (int n=0; n != 6000000; ++n) {
        int x = 7;
        while(x > 6) 
            x = 1 + std::rand()%6;

        results_bias[x-1]++;
    }

    for (int n=0; n !=6; n++) {
        std::cout << results_bias[n] << ' ';
    }
}

Saya kemudian mengambil output ini dan menggunakan chisq.testfungsi di R untuk menjalankan uji Chi-square untuk melihat apakah hasilnya berbeda secara signifikan dari yang diharapkan. Pertanyaan stackexchange ini menjelaskan lebih rinci tentang penggunaan uji chi-square untuk menguji keadilan dadu: Bagaimana saya bisa menguji apakah dadu adil? . Berikut adalah hasil untuk beberapa kali lari:

> ?chisq.test
> unbias <- c(100150, 99658, 100319, 99342, 100418, 100113)
> bias <- c(100049, 100040, 100091, 99966, 100188, 99666 )

> chisq.test(unbias)

Chi-squared test for given probabilities

data:  unbias
X-squared = 8.6168, df = 5, p-value = 0.1254

> chisq.test(bias)

Chi-squared test for given probabilities

data:  bias
X-squared = 1.6034, df = 5, p-value = 0.9008

> unbias <- c(998630, 1001188, 998932, 1001048, 1000968, 999234 )
> bias <- c(1000071, 1000910, 999078, 1000080, 998786, 1001075   )
> chisq.test(unbias)

Chi-squared test for given probabilities

data:  unbias
X-squared = 7.051, df = 5, p-value = 0.2169

> chisq.test(bias)

Chi-squared test for given probabilities

data:  bias
X-squared = 4.319, df = 5, p-value = 0.5045

> unbias <- c(998630, 999010, 1000736, 999142, 1000631, 1001851)
> bias <- c(999803, 998651, 1000639, 1000735, 1000064,1000108)
> chisq.test(unbias)

Chi-squared test for given probabilities

data:  unbias
X-squared = 7.9592, df = 5, p-value = 0.1585

> chisq.test(bias)

Chi-squared test for given probabilities

data:  bias
X-squared = 2.8229, df = 5, p-value = 0.7273

Dalam tiga proses yang saya lakukan, nilai p untuk kedua metode selalu lebih besar dari nilai alfa tipikal yang digunakan untuk menguji signifikansi (0,05). Ini berarti bahwa kami tidak akan menganggap salah satu dari mereka bias. Menariknya, metode yang seharusnya tidak bias memiliki nilai p yang lebih rendah secara konsisten, yang menunjukkan bahwa metode tersebut sebenarnya mungkin lebih bias. Peringatannya adalah bahwa saya hanya melakukan 3 kali lari.

PEMBARUAN: Saat saya menulis jawaban saya, Konrad Rudolph memposting jawaban yang mengambil pendekatan yang sama, tetapi mendapatkan hasil yang sangat berbeda. Saya tidak memiliki reputasi untuk mengomentari jawabannya, jadi saya akan membahasnya di sini. Pertama, hal utama adalah bahwa kode yang dia gunakan menggunakan seed yang sama untuk generator nomor acak setiap kali dijalankan. Jika Anda mengganti bibitnya, Anda justru mendapatkan hasil yang beragam. Kedua, jika Anda tidak mengganti benih, tetapi mengubah jumlah percobaan, Anda juga mendapatkan hasil yang beragam. Cobalah menambah atau mengurangi urutan besarnya untuk melihat apa yang saya maksud. Ketiga, ada beberapa pemotongan atau pembulatan integer yang terjadi di mana nilai yang diharapkan tidak cukup akurat. Mungkin tidak cukup untuk membuat perbedaan, tetapi itu ada.

Pada dasarnya, secara ringkas, dia kebetulan mendapatkan benih yang tepat dan jumlah percobaan yang mungkin dia dapatkan hasil yang salah.

anjama
sumber
Penerapan Anda mengandung kesalahan fatal karena kesalahpahaman di pihak Anda: kutipan tidak dibandingkan rand()%6denganrand()/(1+RAND_MAX)/6 . Sebaliknya, ini membandingkan pengambilan langsung sisa dengan pengambilan sampel penolakan (lihat jawaban lain untuk penjelasan). Akibatnya, kode kedua Anda salah ( whileloop tidak melakukan apa pun). Pengujian statistik Anda juga memiliki masalah (Anda tidak bisa hanya menjalankan pengulangan pengujian Anda untuk ketahanan, Anda tidak melakukan koreksi,…).
Konrad Rudolph
1
@KonradRudolph Saya tidak memiliki perwakilan untuk mengomentari jawaban Anda, jadi saya menambahkannya sebagai pembaruan untuk jawaban saya. Your's juga memiliki kelemahan fatal karena kebetulan menggunakan set seed dan jumlah percobaan setiap run yang memberikan hasil yang salah. Jika Anda menjalankan pengulangan dengan benih yang berbeda, Anda mungkin telah menangkapnya. Tapi ya, Anda benar saat loop tidak melakukan apa-apa, tetapi juga tidak mengubah hasil blok kode tertentu
anjama
Sebenarnya aku menjalankan pengulangan. Benih sengaja tidak ditetapkan karena pengaturan benih acak dengan std::srand(dan tidak ada penggunaan <random>) cukup sulit dilakukan dengan cara yang sesuai standar dan saya tidak ingin kerumitannya mengurangi kode yang tersisa. Ini juga tidak relevan untuk kalkulasi: mengulangi urutan yang sama dalam simulasi sepenuhnya dapat diterima. Tentu saja benih yang berbeda akan memberikan hasil yang berbeda, dan beberapa tidak akan signifikan. Itu sepenuhnya diharapkan berdasarkan bagaimana nilai-p didefinisikan.
Konrad Rudolph
1
Tikus, saya membuat kesalahan dalam pengulangan saya; dan Anda benar, kuantil ke-95 dari pengulangan berjalan cukup dekat dengan p = 0,05 - yaitu persis seperti yang kita harapkan di bawah nol. Singkatnya, implementasi perpustakaan standar saya std::randmenghasilkan simulasi lemparan koin yang sangat bagus untuk d6, di seluruh kisaran benih acak.
Konrad Rudolph
1
Signifikansi statistik hanyalah salah satu bagian dari cerita. Anda memiliki hipotesis nol (terdistribusi secara seragam) dan hipotesis alternatif (bias modulo) —sebenarnya, keluarga hipotesis alternatif, yang diindeks oleh pilihan RAND_MAX, yang menentukan ukuran efek dari bias modulo. Signifikansi statistik adalah probabilitas di bawah hipotesis nol bahwa Anda menolaknya secara salah. Apa kekuatan statistik - probabilitas di bawah hipotesis alternatif bahwa pengujian Anda dengan benar menolak hipotesis nol? Apakah Anda akan mendeteksi rand() % 6seperti ini ketika RAND_MAX = 2 ^ 31 - 1?
Squeamish Ossifrage
2

Seseorang dapat menganggap generator bilangan acak bekerja pada aliran digit biner. Generator mengubah aliran menjadi angka dengan mengirisnya menjadi beberapa bagian. Jika std:randfungsinya bekerja dengan a RAND_MAX32767, maka itu menggunakan 15 bit di setiap irisan.

Ketika seseorang mengambil modul angka antara 0 dan 32767 inklusif, orang menemukan bahwa 5462 '0 dan' 1 tetapi hanya 5461 '2,' 3, '4, dan' 5. Oleh karena itu, hasilnya bias. Semakin besar nilai RAND_MAX, semakin sedikit bias, tetapi itu tidak bisa dihindari.

Yang tidak bias adalah angka dalam rentang [0 .. (2 ^ n) -1]. Anda dapat menghasilkan angka (secara teoritis) yang lebih baik dalam kisaran 0..5 dengan mengekstrak 3 bit, mengubahnya menjadi integer dalam kisaran 0..7 dan menolak 6 dan 7.

Satu harapan bahwa setiap bit dalam aliran bit memiliki peluang yang sama untuk menjadi '0' atau '1' terlepas dari di mana ia berada dalam aliran atau nilai bit lainnya. Ini sangat sulit dalam praktiknya. Berbagai implementasi perangkat lunak PRNG menawarkan kompromi yang berbeda antara kecepatan dan kualitas. Generator kongruensial linier seperti std::randmenawarkan kecepatan tercepat untuk kualitas terendah. Generator kriptografi menawarkan kualitas tertinggi untuk kecepatan terendah.

Simon G.
sumber