Ini adalah tindak lanjut dari pertanyaan yang diposting sebelumnya:
Bagaimana cara menghasilkan nomor acak di C?
Saya ingin dapat menghasilkan nomor acak dari dalam kisaran tertentu, seperti 1 hingga 6 untuk meniru sisi dadu.
Bagaimana saya akan melakukan ini?
Jawaban:
Semua jawaban sejauh ini salah secara matematis. Kembali
rand() % N
tidak secara seragam memberikan angka dalam kisaran[0, N)
kecualiN
membagi panjang interval ke manarand()
kembali (yaitu pangkat 2). Selain itu, orang tidak tahu apakah modulusnyarand()
independen: mungkin saja mereka pergi0, 1, 2, ...
, yang seragam tetapi tidak terlalu acak. Satu-satunya asumsi yang tampaknya masuk akal untuk dibuat adalah yangrand()
mengeluarkan distribusi Poisson: dua subinterval yang tidak tumpang tindih dengan ukuran yang sama kemungkinannya sama dan independen. Untuk sekumpulan nilai yang terbatas, ini menyiratkan distribusi seragam dan juga memastikan bahwa nilairand()
tersebar dengan baik.Ini berarti bahwa satu-satunya cara yang benar untuk mengubah kisaran
rand()
adalah dengan membaginya ke dalam kotak; misalnya, jikaRAND_MAX == 11
dan Anda menginginkan rentang1..6
, Anda harus menetapkan{0,1}
ke 1,{2,3}
ke 2, dan seterusnya. Ini adalah interval yang terputus-putus, berukuran sama dan dengan demikian didistribusikan secara seragam dan independen.Saran untuk menggunakan pembagian floating-point secara matematis masuk akal tetapi pada prinsipnya menderita masalah pembulatan. Mungkin
double
presisi cukup tinggi untuk membuatnya bekerja; mungkin tidak. Saya tidak tahu dan saya tidak ingin memikirkannya; bagaimanapun, jawabannya tergantung pada sistem.Cara yang benar adalah dengan menggunakan aritmatika integer. Artinya, Anda menginginkan sesuatu seperti berikut:
Loop diperlukan untuk mendapatkan distribusi seragam yang sempurna. Misalnya, jika Anda diberi nomor acak dari 0 sampai 2 dan Anda hanya menginginkan satu dari 0 sampai 1, Anda terus menarik sampai tidak mendapatkan 2; tidak sulit untuk memastikan bahwa ini memberikan 0 atau 1 dengan probabilitas yang sama. Metode ini juga dijelaskan dalam tautan yang diberikan no pada jawaban mereka, meskipun kode berbeda. Saya menggunakan
random()
daripadarand()
karena memiliki distribusi yang lebih baik (seperti dicatat oleh halaman manual untukrand()
).Jika Anda ingin mendapatkan nilai acak di luar rentang default
[0, RAND_MAX]
, Anda harus melakukan sesuatu yang rumit. Mungkin yang paling bijaksana adalah untuk mendefinisikan sebuah fungsirandom_extended()
yang menarikn
bit (menggunakanrandom_at_most()
) dan kembali di[0, 2**n)
, dan kemudian menerapkanrandom_at_most()
denganrandom_extended()
di tempatrandom()
(dan2**n - 1
di tempatRAND_MAX
) untuk menarik nilai acak kurang dari2**n
, dengan asumsi Anda memiliki tipe numerik yang dapat menahan seperti sebuah nilai. Akhirnya, tentu saja, Anda bisa mendapatkan nilai dalam[min, max]
menggunakanmin + random_at_most(max - min)
, termasuk nilai negatif.sumber
max - min > RAND_MAX
, yang lebih serius daripada masalah yang saya sebutkan di atas (misalnya VC ++RAND_MAX
hanya memiliki 32.767).do {} while()
.Mengikuti jawaban @Ryan Reich, saya pikir saya akan menawarkan versi bersih saya. Pemeriksaan batas pertama tidak diperlukan untuk pemeriksaan batas kedua, dan saya telah membuatnya berulang daripada rekursif. Ini mengembalikan nilai dalam rentang [min, max], di mana
max >= min
dan1+max-min < RAND_MAX
.sumber
limit
int (danbucket
juga opsional ) sejakRAND_MAX / range
<INT_MAX
danbuckets * range
<=RAND_MAX
. EDIT: Saya telah mengirimkan dan mengedit proposal.Berikut adalah rumus jika Anda mengetahui nilai maks dan min dari suatu rentang, dan Anda ingin menghasilkan angka yang disertakan di antara rentang tersebut:
sumber
int
melimpah denganmax+1-min
.Lihat di sini untuk opsi lain.
sumber
(((max-min+1)*rand())/RAND_MAX)+min
dan mungkin mendapatkan distribusi yang sama persis (dengan asumsi bahwa RAND_MAX cukup kecil dibandingkan int untuk tidak meluap).max + 1
, jika salahrand() == RAND_MAX
, ataurand()
sangat dekat denganRAND_MAX
dan kesalahan floating-point mendorong hasil akhir melewatimax + 1
. Untuk amannya, Anda harus memeriksa apakah hasilnya dalam jangkauan sebelum mengembalikannya.RAND_MAX + 1.0
. Saya masih tidak yakin itu cukup baik untuk mencegahmax + 1
pengembalian, meskipun: khususnya,+ min
pada akhirnya melibatkan putaran yang bisa menghasilkanmax + 1
nilai rand () yang besar. Lebih aman untuk meninggalkan pendekatan ini sama sekali dan menggunakan aritmatika integer.RAND_MAX
diganti denganRAND_MAX+1.0
sebagai Christoph menyarankan, maka saya percaya bahwa ini aman asalkan+ min
dilakukan dengan menggunakan aritmatika integer:return (unsigned int)((max - min + 1) * scaled) + min
. Alasan (tidak jelas) adalah bahwa dengan asumsi IEEE 754 aritmatika dan round-half-to-even, (dan itu jugamax - min + 1
dapat direpresentasikan sebagai double, tapi itu akan benar pada mesin biasa), itu selalu benarx * scaled < x
untuk dobel positifx
dan dobel apa pun yangscaled
memuaskan0.0 <= scaled && scaled < 1.0
.randr(0, UINT_MAX)
: selalu menghasilkan 0.Tidakkah Anda hanya melakukan:
%
adalah operator modulus. Pada dasarnya itu hanya akan membagi dengan 6 dan mengembalikan sisanya ... dari 0 - 5sumber
rand()
menyertakan bit orde rendah dari status generator (jika menggunakan LCG). Saya belum pernah melihat satu pun sejauh ini — semuanya (ya, termasuk MSVC dengan RAND_MAX hanya 32767) menghapus bit orde rendah. Penggunaan modulus tidak disarankan untuk alasan lain, yaitu karena hal itu mendistorsi distribusi untuk mendukung bilangan yang lebih kecil.Bagi mereka yang memahami masalah bias tetapi tidak tahan dengan waktu proses yang tidak terduga dari metode berbasis penolakan, rangkaian ini menghasilkan bilangan bulat acak yang semakin tidak bias dalam
[0, n-1]
interval:Ia melakukannya dengan mensintesis
i * log_2(RAND_MAX + 1)
bit acak titik tetap presisi tinggi (di manai
jumlah iterasi) dan melakukan perkalian panjang dengann
.Ketika jumlah bit cukup besar dibandingkan
n
, biasnya menjadi sangat kecil.Tidak masalah jika
RAND_MAX + 1
kurang darin
(seperti dalam pertanyaan ini ), atau jika itu bukan pangkat dua, tetapi kehati-hatian harus dilakukan untuk menghindari luapan bilangan bulat jikaRAND_MAX * n
besar.sumber
RAND_MAX
seringINT_MAX
, jadiRAND_MAX + 1
-> UB (seperti INT_MIN)RAND_MAX * n
besar". Anda perlu mengatur untuk menggunakan jenis yang sesuai dengan kebutuhan Anda.RAND_MAX
sering kaliINT_MAX
" Ya, tetapi hanya pada sistem 16 bit! Arsitektur yang cukup modern akan menempatkanINT_MAX
pada 2 ^ 32/2 danRAND_MAX
pada 2 ^ 16 / 2. Apakah ini asumsi yang salah?int
kompiler 32-bit , saya temukanRAND_MAX == 32767
di satu danRAND_MAX == 2147483647
di lain. Pengalaman saya secara keseluruhan (dekade)RAND_MAX == INT_MAX
lebih sering. Jadi tidak setuju bahwa arsitektur 32-bit cukup modern tentu akan memilikiRAND_MAX
di2^16 / 2
. Karena spesifikasi C memungkinkan32767 <= RAND_MAX <= INT_MAX
, saya mengkodekannya lebih dari sekedar kecenderungan.Untuk menghindari bias modulo (disarankan dalam jawaban lain) Anda selalu dapat menggunakan:
Dimana "MAX" adalah batas atas dan "MIN" adalah batas bawah. Misalnya, untuk angka antara 10 dan 20:
Solusi sederhana dan lebih baik daripada menggunakan "rand ()% N".
sumber
#include <bsd/stdlib.h>
terlebih dahulu. Juga, tahu bagaimana cara mendapatkannya di Windows tanpa MinGW atau CygWin?Berikut adalah algoritma yang sedikit lebih sederhana daripada solusi Ryan Reich:
sumber
RAND_MAX + 1
dapat dengan mudah meluapint
tambahan. Dalam hal ini,(RAND_MAX + 1) % range
akan menghasilkan hasil yang dipertanyakan. Pertimbangkan(RAND_MAX + (uint32_t)1)
Meski Ryan benar, solusinya bisa lebih sederhana berdasarkan apa yang diketahui tentang sumber keacakan. Untuk menyatakan kembali masalahnya:
[0, MAX)
dengan distribusi seragam.[rmin, rmax]
mana0 <= rmin < rmax < MAX
.Menurut pengalaman saya, jika jumlah tempat sampah (atau "kotak") secara signifikan lebih kecil dari kisaran nomor aslinya, dan sumber aslinya kuat secara kriptografis - tidak perlu melalui semua rigamarole itu, dan pembagian modulo sederhana akan cukup (seperti
output = rnd.next() % (rmax+1)
, jikarmin == 0
), dan menghasilkan bilangan acak yang didistribusikan secara seragam "cukup", dan tanpa kehilangan kecepatan. Faktor kuncinya adalah sumber keacakan (yaitu, anak-anak, jangan mencobanya di rumah denganrand()
).Berikut contoh / bukti cara kerjanya dalam praktik. Saya ingin menghasilkan angka acak dari 1 hingga 22, memiliki sumber yang kuat secara kriptografis yang menghasilkan byte acak (berdasarkan Intel RDRAND). Hasilnya adalah:
Ini hampir sama dengan seragam yang saya butuhkan untuk tujuan saya (lemparan dadu yang adil, menghasilkan buku kode yang kuat secara kriptografis untuk mesin sandi PD II seperti http://users.telenet.be/d.rijmenants/en/kl-7sim.htm , dll. ). Outputnya tidak menunjukkan bias yang berarti.
Berikut adalah sumber penghasil bilangan acak yang kuat secara kriptografik (benar): Pembangkit Nomor Acak Digital Intel dan kode sampel yang menghasilkan bilangan acak 64-bit (tidak bertanda).
Saya mengompilasinya di Mac OS X dengan clang-6.0.1 (lurus), dan dengan gcc-4.8.3 menggunakan tanda "-Wa, q" (karena GAS tidak mendukung instruksi baru ini).
sumber
gcc randu.c -o randu -Wa,q
(GCC 5.3.1 di Ubuntu 16) atauclang randu.c -o randu
(Clang 3.8.0) berfungsi, tetapi membuang inti saat runtime denganIllegal instruction (core dumped)
. Ada ide?rand()
. Saya mencoba beberapa tes dan memposting pertanyaan ini tetapi saya belum dapat menemukan jawaban yang pasti.Seperti dikatakan sebelumnya, modulo tidak cukup karena mengganggu distribusi. Inilah kode saya yang menutupi bit dan menggunakannya untuk memastikan distribusi tidak miring.
Kode sederhana berikut memungkinkan Anda melihat distribusi:
sumber
v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;
Saya mengerti bahwa modulo adalah operasi yang jauh lebih lambat daripada masking, tapi saya masih berpikir ..... itu harus diuji.rand()
mengembalikanint
dalam kisaran[0..RAND_MAX]
. Rentang tersebut dapat dengan mudah menjadi subrentanguint32_t
dan kemudianrandomInRange(0, ,b)
tidak pernah menghasilkan nilai dalam rentang tersebut(INT_MAX...b]
.Akan mengembalikan angka floating point dalam kisaran [0,1]:
sumber