Menghasilkan bilangan bulat acak dari suatu rentang

158

Saya membutuhkan fungsi yang akan menghasilkan bilangan bulat acak dalam rentang yang diberikan (termasuk nilai batas). Saya tidak persyaratan kualitas / keacakan yang tidak masuk akal, saya memiliki empat persyaratan:

  • Saya perlu cepat. Proyek saya perlu menghasilkan jutaan (atau kadang-kadang bahkan puluhan juta) angka acak dan fungsi generator saya saat ini telah terbukti menjadi hambatan.
  • Saya membutuhkannya agar seragam (penggunaan rand () baik-baik saja).
  • rentang min-max bisa apa saja dari <0, 1> hingga <-32727, 32727>.
  • itu harus ditaburkan.

Saat ini saya memiliki kode C ++ berikut:

output = min + (rand() * (int)(max - min) / RAND_MAX)

Masalahnya adalah, itu tidak benar-benar seragam - maks dikembalikan hanya ketika rand () = RAND_MAX (untuk Visual C ++ itu 1/32727). Ini adalah masalah utama untuk rentang kecil seperti <-1, 1>, di mana nilai terakhir hampir tidak pernah dikembalikan.

Jadi saya mengambil pena dan kertas dan menghasilkan formula berikut (yang dibangun di atas (int) (n + 0,5) trik pembulatan bilangan bulat):

masukkan deskripsi gambar di sini

Tapi itu masih tidak memberi saya distribusi seragam. Berjalan berulang dengan 10.000 sampel memberi saya rasio 37:50:13 untuk nilai nilai -1, 0. 1.

Bisakah Anda menyarankan formula yang lebih baik? (atau bahkan seluruh fungsi generator nomor pseudo-acak)

Matěj Zábský
sumber
3
@ Bill MaGriff: ya. Ini memiliki masalah yang sama. Versi yang disederhanakan adalah: bagaimana Anda bisa membagi 10 permen di antara 3 anak secara merata (tanpa merusak permen)? Jawabannya adalah, Anda tidak bisa - Anda harus memberikan tiga untuk setiap anak, dan tidak memberikan yang kesepuluh kepada siapa pun.
Jerry Coffin
5
Sudahkah Anda melihat Boost.Random ?
Fred Nurk
3
Lihat artikel Andrew Koenig "Masalah sederhana yang hampir tidak pernah diselesaikan dengan benar": drdobbs.com/blog/archives/2010/11/a_simple_proble.html
Gene Bushuyev
1
@Gene Bushuyev: Andrew dan saya telah membicarakan hal ini cukup lama sekarang. Lihat: groups.google.com/group/comp.lang.c++/browse_frm/thread/… , dan: groups.google.com/group/comp.os.ms-windows.programmer.tools.mfc/…
Jerry Coffin

Jawaban:

105

Solusi cepat, agak lebih baik dari milik Anda, tetapi masih belum terdistribusi dengan benar

output = min + (rand() % static_cast<int>(max - min + 1))

Kecuali ketika ukuran kisaran adalah kekuatan 2, metode ini menghasilkan angka terdistribusi tidak bias yang bias terlepas dari kualitas rand(). Untuk uji komprehensif kualitas metode ini, baca ini .

Markus B
sumber
2
Terima kasih, ini tampaknya cukup baik bagi saya dari tes cepat - distribusinya untuk -1, 0, 1 hampir 33:33:33.
Matěj Zábský
3
Ia mengembalikan nilai maks selalu. Apakah saya melewatkan sesuatu di sini? : |
rohan-patel
15
rand()harus dianggap berbahaya di C ++ ada banyak cara yang lebih baik untuk mendapatkan sesuatu yang terdistribusi secara seragam dan sebenarnya acak.
Mgetz
1
Apakah itu benar-benar mengembalikan angka yang benar dalam kisaran 100% dari waktu? Saya telah menemukan beberapa jawaban stackoverflow lainnya di sini yang menggunakan rekursi untuk melakukannya "dengan cara yang benar": stackoverflow.com/a/6852396/623622
Czarek Tomczak
2
Karena ini adalah jawaban yang sangat tervvotasikan (dari yang diinginkan), yang tampaknya menjadi sumber informasi yang dapat diandalkan bagi banyak pembaca baru, saya pikir sangat penting untuk menyebutkan kualitas dan potensi bahaya dari solusi ini, jadi saya mengedit.
plasmacel
297

Jawaban C ++ yang paling sederhana (dan karenanya terbaik) (menggunakan standar 2011) adalah

#include <random>

std::random_device rd;     // only used once to initialise (seed) engine
std::mt19937 rng(rd());    // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased

auto random_integer = uni(rng);

Tidak perlu menemukan kembali roda. Tidak perlu khawatir tentang bias. Tidak perlu khawatir menggunakan waktu sebagai benih acak.

Walter
sumber
1
Sekarang ini seharusnya jawabannya . Referensi pembuatan angka pseudo-acak untuk lebih banyak fitur.
alextoind
8
Saya setuju pada yang "paling sederhana" (dan paling idiomatis), bukan pada yang "terbaik". Sayangnya Standar tidak memberikan jaminan random_device, yang mungkin benar-benar rusak dalam beberapa kasus . Selain itu, mt19937walaupun merupakan pilihan tujuan umum yang sangat baik, bukan yang tercepat dari generator berkualitas baik (lihat perbandingan ini ) dan karenanya mungkin bukan kandidat yang ideal untuk OP.
Alberto M
1
@AlbertoM Sayangnya, perbandingan yang Anda referensikan tidak memberikan detail yang cukup dan tidak dapat diproduksi ulang, yang membuatnya meragukan (apalagi, ini dari 2015, sedangkan jawaban saya kembali ke 2013). Mungkin benar bahwa ada metode yang lebih baik di sekitar (dan mudah-mudahan di masa depan, minstdakan menjadi metode semacam itu), tapi itu kemajuan. Adapun implementasi yang buruk dari random_device- itu mengerikan dan harus dianggap bug (mungkin juga dari standar C ++, jika memungkinkan).
Walter
1
Saya sangat setuju dengan anda; Saya sebenarnya tidak ingin mengkritik solusi Anda semata , hanya ingin memperingatkan pembaca biasa bahwa jawaban pasti tentang masalah ini, meskipun ada janji-janji dari C ++ 11, belum ditulis. Saya akan memposting tinjauan umum pada 2015 sebagai jawaban dari pertanyaan terkait .
Alberto M
1
Itu "paling sederhana"? Bisakah Anda menguraikan mengapa hal yang jelas jauh lebih sederhana rand()bukanlah suatu pilihan, dan apakah itu penting untuk penggunaan yang tidak kritis, seperti menghasilkan indeks pivot acak? Juga, apakah saya harus khawatir tentang membangun random_device/ mt19937/ uniform_int_distributiondalam fungsi loop / inline yang ketat? Haruskah saya lebih suka membagikannya?
bluenote10
60

Jika kompiler Anda mendukung C ++ 0x dan menggunakannya adalah opsi untuk Anda, maka <random>tajuk standar baru cenderung memenuhi kebutuhan Anda. Ini memiliki kualitas tinggi uniform_int_distributionyang akan menerima batas minimum dan maksimum (termasuk yang Anda butuhkan), dan Anda dapat memilih di antara berbagai generator angka acak untuk dihubungkan ke distribusi itu.

Berikut adalah kode yang menghasilkan sejuta acak yang intdidistribusikan secara seragam di [-57, 365]. Saya telah menggunakan <chrono>fasilitas std baru untuk mengukur waktu seperti yang Anda sebutkan kinerja adalah perhatian utama bagi Anda.

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double> sec;
    Clock::time_point t0 = Clock::now();
    const int N = 10000000;
    typedef std::minstd_rand G;
    G g;
    typedef std::uniform_int_distribution<> D;
    D d(-57, 365);
    int c = 0;
    for (int i = 0; i < N; ++i) 
        c += d(g);
    Clock::time_point t1 = Clock::now();
    std::cout << N/sec(t1-t0).count() << " random numbers per second.\n";
    return c;
}

Bagi saya (2,8 GHz Intel Core i5) ini mencetak:

2.10268e + 07 angka acak per detik.

Anda dapat menyemai generator dengan memasukkan int ke konstruktornya:

    G g(seed);

Jika nanti Anda menemukan bahwa intitu tidak mencakup rentang yang Anda butuhkan untuk distribusi Anda, ini dapat diperbaiki dengan mengubah uniform_int_distributionseperti itu (misalnya ke long long):

    typedef std::uniform_int_distribution<long long> D;

Jika nanti Anda menemukan bahwa minstd_randgenerator itu tidak berkualitas cukup tinggi, itu juga dapat dengan mudah diganti. Misalnya:

    typedef std::mt19937 G;  // Now using mersenne_twister_engine

Memiliki kontrol terpisah atas generator angka acak, dan distribusi acak bisa sangat membebaskan.

Saya juga telah menghitung (tidak ditampilkan) 4 "momen" pertama dari distribusi ini (menggunakan minstd_rand) dan membandingkannya dengan nilai-nilai teoritis dalam upaya untuk mengukur kualitas distribusi:

min = -57
max = 365
mean = 154.131
x_mean = 154
var = 14931.9
x_var = 14910.7
skew = -0.00197375
x_skew = 0
kurtosis = -1.20129
x_kurtosis = -1.20001

( x_Awalan mengacu pada "yang diharapkan")

Howard Hinnant
sumber
3
Jawaban ini bisa menggunakan potongan kode ringkasan pendek yang hanya menunjukkan kode yang benar-benar diperlukan untuk menghasilkan bilangan bulat acak dari suatu rentang.
arekolek
Masalahnya menjadi lebih mudah dengan fakta bahwa min dan maks distribusi tidak pernah berubah. Bagaimana jika Anda harus membuat ddi setiap iterasi dengan batasan yang berbeda? Berapa banyak memperlambat loop?
quant_dev
16

Mari kita bagi masalah menjadi dua bagian:

  • Hasilkan nomor acak n dalam rentang 0 hingga (maks-mnt).
  • Tambahkan min ke nomor itu

Bagian pertama jelas yang paling sulit. Mari kita asumsikan bahwa nilai pengembalian rand () sangat seragam. Menggunakan modulo akan menambah bias ke (RAND_MAX + 1) % (max-min+1)angka pertama . Jadi jika kita secara ajaib bisa berubah RAND_MAXmenjadiRAND_MAX - (RAND_MAX + 1) % (max-min+1) , tidak ada lagi akan bias.

Ternyata kita dapat menggunakan intuisi ini jika kita bersedia mengizinkan pseudo-nondeterminisme ke dalam waktu berjalan dari algoritma kita. Setiap kali rand () mengembalikan nomor yang terlalu besar, kami cukup meminta nomor acak lain sampai kami mendapatkan nomor yang cukup kecil.

Waktu berjalan sekarang didistribusikan secara geometris , dengan nilai yang diharapkan di 1/pmana pprobabilitas mendapatkan angka yang cukup kecil pada percobaan pertama. Karena RAND_MAX - (RAND_MAX + 1) % (max-min+1)selalu kurang dari (RAND_MAX + 1) / 2, kita tahu itu p > 1/2, sehingga jumlah iterasi yang diharapkan akan selalu kurang dari dua untuk rentang apa pun. Seharusnya dimungkinkan untuk menghasilkan puluhan juta angka acak dalam waktu kurang dari satu detik pada CPU standar dengan teknik ini.

EDIT:

Meskipun hal di atas secara teknis benar, jawaban DSimon mungkin lebih berguna dalam praktiknya. Anda seharusnya tidak menerapkan hal ini sendiri. Saya telah melihat banyak implementasi dari sampel penolakan dan seringkali sangat sulit untuk melihat apakah itu benar atau tidak.

Jørgen Fogh
sumber
Untuk kelengkapan: Ini adalah Sampel Penolakan .
etarion
3
Fakta yang menyenangkan: Joel Spolsky pernah menyebut versi pertanyaan ini sebagai contoh apa yang baik untuk dijawab oleh StackOverflow. Aku melihat melalui jawaban di situs melibatkan pengambilan sampel penolakan pada waktu itu dan setiap satu satu tidak benar.
Jørgen Fogh
13

Bagaimana dengan Twister Mersenne ? Implementasi boost agak mudah digunakan dan diuji dengan baik di banyak aplikasi dunia nyata. Saya telah menggunakannya sendiri di beberapa proyek akademik seperti kecerdasan buatan dan algoritma evolusi.

Inilah contoh mereka di mana mereka membuat fungsi sederhana untuk menggulung dadu enam sisi:

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>

boost::mt19937 gen;

int roll_die() {
    boost::uniform_int<> dist(1, 6);
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(gen, dist);
    return die();
}

Oh, dan inilah beberapa mucikari dari generator ini kalau-kalau Anda tidak yakin Anda harus menggunakannya pada yang jauh lebih rendah rand():

The Mersenne Twister adalah generator "angka acak" yang ditemukan oleh Makoto Matsumoto dan Takuji Nishimura; situs web mereka mencakup banyak implementasi algoritma.

Pada dasarnya, Mersenne Twister adalah register geser linier-umpan balik yang sangat besar. Algoritma ini beroperasi pada seed 19.937 bit, disimpan dalam array 624 elemen dari integer 32-bit unsigned. Nilai 2 ^ 19937-1 adalah prime Mersenne; teknik untuk memanipulasi benih didasarkan pada algoritma "memutar" yang lebih tua - maka nama "Mersenne Twister".

Aspek menarik dari Mersenne Twister adalah penggunaan operasi biner - yang bertentangan dengan penggandaan yang memakan waktu - untuk menghasilkan angka. Algoritma juga memiliki periode yang sangat panjang, dan granularity yang baik. Ini cepat dan efektif untuk aplikasi non-kriptografi.

Aphex
sumber
1
Twister Mersenne adalah generator yang baik, tetapi masalah yang dia hadapi tetap ada, terlepas dari generator yang mendasarinya.
Jerry Coffin
Saya tidak ingin menggunakan Boost hanya untuk generator acak, karena (karena proyek saya adalah perpustakaan) itu berarti memperkenalkan ketergantungan lain pada proyek. Saya mungkin akan terpaksa menggunakannya di masa depan, jadi saya bisa beralih ke generator ini.
Matěj Zábský
1
@ Jerry Coffin Masalah apa? Saya menawarkannya karena memenuhi semua persyaratannya: cepat, seragam (menggunakan boost::uniform_intdistribusi), Anda dapat mengubah rentang min max menjadi apa pun yang Anda suka, dan itu dapat diunggulkan.
Aphex
@ mzabsky Saya mungkin tidak akan membiarkan itu menghentikan saya, ketika saya harus mengirimkan proyek saya kepada profesor saya untuk diserahkan, saya hanya menyertakan file header boost relevan yang saya gunakan; Anda tidak harus mengemas seluruh pustaka boost 40MB dengan kode Anda. Tentu saja dalam kasus Anda ini mungkin tidak layak karena alasan lain seperti hak cipta ...
Aphex
@Aphex Proyek saya bukan simulator ilmiah atau sesuatu yang benar-benar membutuhkan distribusi yang seragam. Saya menggunakan generator lama selama 1,5 tahun tanpa masalah, saya hanya melihat distribusi bias ketika saya pertama kali membutuhkannya untuk menghasilkan angka dari kisaran yang sangat kecil (3 dalam kasus ini). Kecepatannya masih menjadi argumen untuk mempertimbangkan solusi boost. Saya akan melihat ke lisensi untuk melihat apakah saya bisa menambahkan beberapa file yang diperlukan untuk proyek saya - saya suka "Checkout -> F5 -> ready to use" seperti sekarang.
Matěj Zábský
11
int RandU(int nMin, int nMax)
{
    return nMin + (int)((double)rand() / (RAND_MAX+1) * (nMax-nMin+1));
}

Ini adalah pemetaan 32768 integer ke (nMax-nMin + 1) integer. Pemetaan akan cukup baik jika (nMax-nMin + 1) kecil (seperti dalam kebutuhan Anda). Perhatikan bahwa jika (nMax-nMin + 1) besar, pemetaan tidak akan berfungsi (Misalnya - Anda tidak dapat memetakan 32768 nilai ke nilai 30000 dengan probabilitas sama). Jika rentang tersebut diperlukan - Anda harus menggunakan sumber acak 32-bit atau 64-bit, alih-alih hasil rand 15-bit (), atau abaikan rand () yang berada di luar jangkauan.

Lior Kogan
sumber
Meskipun tidak populer, ini juga yang saya gunakan untuk proyek non-ilmiah saya. Mudah dimengerti (Anda tidak perlu gelar matematika) dan berkinerja memadai (tidak pernah harus profil kode apa pun yang menggunakannya). :) Dalam hal rentang besar, saya kira kita dapat merangkai dua nilai rand () bersama-sama dan mendapatkan nilai 30-bit untuk bekerja dengannya (dengan asumsi RAND_MAX = 0x7fff, yaitu 15 bit acak)
efotinis
ubah RAND_MAXke (double) RAND_MAXuntuk menghindari peringatan integer overflow.
alex
4

Ini adalah versi yang tidak bias yang menghasilkan angka di [low, high]:

int r;
do {
  r = rand();
} while (r < ((unsigned int)(RAND_MAX) + 1) % (high + 1 - low));
return r % (high + 1 - low) + low;

Jika rentang Anda cukup kecil, tidak ada alasan untuk menembolok sisi kanan perbandingan di doloop.

Jeremiah Willcock
sumber
IMO, tidak ada solusi yang disajikan benar-benar banyak perbaikan. Solusi berbasis loop-nya berfungsi, tetapi kemungkinan tidak efisien, terutama untuk rentang kecil seperti OP yang membahas. Solusi menyimpang seragamnya sebenarnya tidak menghasilkan penyimpangan seragam sama sekali. Paling-paling itu semacam menyamarkan kurangnya keseragaman.
Jerry Coffin
@ Jerry: Silakan periksa versi baru.
Jeremiah Willcock
Saya agak ragu tentang itu bekerja dengan benar. Mungkin, tetapi kebenaran tidak tampak jelas, setidaknya bagi saya.
Jerry Coffin
@ Jerry: Inilah alasan saya: Asumsikan kisarannya adalah [0, h)untuk kesederhanaan. Panggilan rand()memiliki RAND_MAX + 1nilai pengembalian yang memungkinkan; mengambil rand() % hruntuh (RAND_MAX + 1) / hdari mereka ke masing-masing nilai houtput, kecuali bahwa (RAND_MAX + 1) / h + 1mereka dipetakan ke nilai yang kurang dari (RAND_MAX + 1) % h(karena siklus parsial terakhir melalui houtput). Karena itu kami menghapus (RAND_MAX + 1) % hkemungkinan output untuk mendapatkan distribusi yang tidak bias.
Jeremiah Willcock
3

Saya merekomendasikan perpustakaan Boost.Random , sangat rinci dan terdokumentasi dengan baik, memungkinkan Anda menentukan secara spesifik distribusi apa yang Anda inginkan, dan dalam skenario non-kriptografi sebenarnya dapat mengungguli implementasi rand C library yang khas.

DSimon
sumber
1

anggap min dan maks adalah nilai int, [dan] berarti sertakan nilai ini, (dan) berarti tidak termasuk nilai ini, gunakan di atas untuk mendapatkan nilai yang tepat menggunakan c ++ rand ()

referensi: untuk () mendefinisikan], kunjungi:

https://en.wikipedia.org/wiki/Interval_(mathematics)

untuk fungsi rand dan srand atau yang ditentukan RAND_MAX, kunjungi:

http://en.cppreference.com/w/cpp/numeric/random/rand

[min, maks]

int randNum = rand() % (max - min + 1) + min

(minimum, maks)

int randNum = rand() % (max - min) + min + 1

[min, maks)

int randNum = rand() % (max - min) + min

(minimum, maks)

int randNum = rand() % (max - min - 1) + min + 1
Huang Kun
sumber
0

Dalam thread penolakan sampel ini sudah dibahas, tetapi saya ingin menyarankan satu optimasi berdasarkan fakta yang rand() % 2^somethingtidak menimbulkan bias seperti yang telah disebutkan di atas.

Algoritma ini sangat sederhana:

  • menghitung kekuatan terkecil 2 lebih besar dari panjang interval
  • acak satu nomor dalam interval "baru" itu
  • mengembalikan nomor itu jika kurang dari panjang interval asli
    • tolak sebaliknya

Ini kode contoh saya:

int randInInterval(int min, int max) {
    int intervalLen = max - min + 1;
    //now calculate the smallest power of 2 that is >= than `intervalLen`
    int ceilingPowerOf2 = pow(2, ceil(log2(intervalLen)));

    int randomNumber = rand() % ceilingPowerOf2; //this is "as uniform as rand()"

    if (randomNumber < intervalLen)
        return min + randomNumber;      //ok!
    return randInInterval(min, max);    //reject sample and try again
} 

Ini bekerja dengan baik terutama untuk interval kecil, karena kekuatan 2 akan "lebih dekat" dengan panjang interval nyata, sehingga jumlah kesalahan akan lebih kecil.

PS
Jelas menghindari rekursi akan lebih efisien (tidak perlu menghitung berulang-ulang log plafon ..) tapi saya pikir itu lebih mudah dibaca untuk contoh ini.

Pado
sumber
0

Perhatikan bahwa dalam sebagian besar saran, nilai acak awal yang Anda dapatkan dari fungsi rand (), yang biasanya dari 0 hingga RAND_MAX, terbuang sia-sia. Anda hanya membuat satu angka acak, sementara ada prosedur suara yang bisa memberi Anda lebih banyak.

Asumsikan bahwa Anda menginginkan wilayah [min, maks] angka bilangan bulat acak. Kita mulai dari [0, maks-mnt]

Ambil basis b = maks-min + 1

Mulai dari mewakili angka yang Anda dapatkan dari rand () di pangkalan b.

Dengan cara itu Anda mendapatkan lantai (log (b, RAND_MAX)) karena setiap digit dalam basis b, kecuali mungkin yang terakhir, mewakili angka acak dalam kisaran [0, maks-mnt].

Tentu saja perubahan terakhir ke [min, maks] sederhana untuk setiap angka acak r + min.

int n = NUM_DIGIT-1;
while(n >= 0)
{
    r[n] = res % b;
    res -= r[n];
    res /= b;
    n--;
}

Jika NUM_DIGIT adalah jumlah digit dalam basis b yang dapat Anda ekstrak dan itu

NUM_DIGIT = floor(log(b,RAND_MAX))

maka yang di atas adalah sebagai implementasi sederhana mengekstraksi NUM_DIGIT angka acak dari 0 ke b-1 dari satu RAND_MAX nomor acak yang menyediakan b <RAND_MAX.

alex.peter
sumber
-1

Rumus untuk ini sangat sederhana, jadi coba ungkapan ini,

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0
Sohail xIN3N
sumber
2
Seluruh masalah menggunakan rand C / C ++ yang mengembalikan integer dalam rentang yang ditentukan oleh runtime. Seperti yang diperlihatkan dalam utas ini, pemetaan bilangan bulat acak dari [0, RAND_MAX] ke [MIN, MAX] tidak sepenuhnya mudah, jika Anda ingin menghindari penghancuran properti atau kinerja statistik mereka. Jika Anda memiliki dua kali lipat dalam rentang [0, 1], pemetaannya mudah.
Matěj Zábský
2
Jawaban Anda salah, Anda sebaiknya menggunakan modulus:int num = (int) rand() % (max - min) + min;
Jaime Ivan Cervantes
-2

Ungkapan berikut harus tidak bias jika saya tidak salah:

std::floor( ( max - min + 1.0 ) * rand() ) + min;

Saya berasumsi di sini bahwa rand () memberi Anda nilai acak dalam kisaran antara 0,0 dan 1,0 TIDAK termasuk 1,0 dan bahwa max dan min adalah bilangan bulat dengan kondisi bahwa min <max.

Moritz
sumber
std::floorkembali double, dan kami membutuhkan nilai integer di sini. Saya hanya akan menggunakan untuk intbukannya menggunakan std::floor.
musiphil