Генерування випадкових цілих чисел з діапазону


157

Мені потрібна функція, яка б генерувала випадкове ціле число в заданому діапазоні (включаючи граничні значення). Я не пред'являю необґрунтованих вимог до якості / випадковості, у мене є чотири вимоги:

  • Мені це потрібно, щоб бути швидким. Мій проект повинен генерувати мільйони (а іноді навіть десятки мільйонів) випадкових чисел, і моя поточна функція генератора виявилася вузьким місцем.
  • Мені потрібно, щоб вона була достатньо рівномірною (використання rand () ідеально).
  • діапазони min-max можуть бути від 0, 1> до <-32727, 32727>.
  • він повинен бути насіннєвим.

На даний момент у мене є наступний код C ++:

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

Проблема полягає в тому, що він насправді не є рівномірним - max повертається лише тоді, коли rand () = RAND_MAX (для Visual C ++ це 1/32727). Це головне питання для невеликих діапазонів, таких як <-1, 1>, де останнє значення майже ніколи не повертається.

Тому я схопив ручку та папір і придумав таку формулу (яка заснована на (int) (n + 0,5) цілому фокусі округлення):

введіть тут опис зображення

Але це все ще не дає мені рівномірного розподілу. Повторні пробіжки з 10000 зразками дають мені співвідношення 37:50:13 для значень значень -1, 0. 1.

Не могли б ви запропонувати кращу формулу? (або навіть цілу функцію генератора псевдовипадкових чисел)



3
@Bill MaGriff: так. У нього така ж проблема. Спрощена версія така: як можна розділити 10 штук цукерки серед 3 дітей рівномірно (не ламаючи жодної цукерки)? Відповідь, ви не можете - потрібно давати по три дитини кожній дитині, а десяту не давати нікому.
Джеррі Труну

5
Ви подивилися на Boost.Random ?
Фред Нурк

3
Перевірте статтю Ендрю Кеніга "Проста проблема, яка майже ніколи не вирішується правильно": drdobbs.com/blog/archives/2010/11/a_simple_proble.html
Джин Бушуєв

1
@ Гене Бушуєв: Ми з Ендрю вже досить давно працюємо на цю тему. Дивіться: groups.google.com/group/comp.lang.c++/browse_frm/thread/… та: groups.google.com/group/comp.os.ms-windows.programmer.tools.mfc/…
Джеррі Коффін

Відповіді:


105

Швидкий, дещо кращий, ніж ваш, але все ж не належно рівномірний розподілене рішення

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

За винятком випадків, коли розмір діапазону є потужністю 2, цей спосіб виробляє упереджені нерівномірні розподілені числа незалежно від якостіrand(). Для всебічного випробування якості цього методу, будь ласка, прочитайте це .


2
Дякую, це здається мені досить хорошим із швидких тестів - його розподіл для -1, 0, 1 майже 33:33:33.
Matěj Zábský

3
Завжди повертає максимальне значення. Я чогось тут пропускаю? : |
rohan-patel

15
rand()В C ++ слід вважати шкідливим, є набагато кращі способи отримати рівномірно розподілене та фактично випадкове.
Mgetz

1
Чи дійсно повертає правильне число в межах 100% часу? Тут я знайшов іншу відповідь stackoverflow, яка використовує рекурсію, щоб зробити це "правильним шляхом": stackoverflow.com/a/6852396/623622
Czarek Tomczak

2
Оскільки це відповідь на високий рівень (ніж бажано), яка здається надійним джерелом інформації для багатьох нових читачів, я думаю, що це дуже важливо згадати про якість та потенційну небезпеку цього рішення, тому я зробив редагування.
плазмацел

296

Найпростіша (а отже найкраща) відповідь на C ++ (використовуючи стандарт 2011 року)

#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);

Не потрібно заново вигадувати колесо. Не потрібно турбуватися про упередженість. Не потрібно турбуватися про використання часу як випадкового насіння.


1
Сьогодні це має бути відповіддю . Псевдовипадкова посилання на генерацію чисел для отримання додаткових функцій.
alextoind

8
Я згоден на "найпростіший" (і найбільш ідіоматичний), а не на "найкращий". На жаль, Стандарт не дає жодних гарантій random_device, які можуть бути повністю порушені в деяких випадках . Більше того, mt19937хоча дуже хороший загальний вибір, це не найшвидший з генераторів високої якості (див. Це порівняння ) і, отже, може бути не ідеальним кандидатом на проведення ОП.
Альберто М

1
@AlbertoM На жаль, порівняння, на яке ви посилаєтесь, не містить достатньої кількості деталей і не підлягає відновленню, що робить це сумнівним (більше того, це з 2015 року, а моя відповідь датується 2013 роком). Цілком може бути правдою, що навколо є кращі методи (і, сподіваємось, у майбутньому minstdце буде такий метод), але це вже прогрес. Щодо поганої реалізації random_device- це жахливо, і його слід вважати помилкою (можливо, також стандартом C ++, якщо це дозволяє).
Вальтер

1
Я повністю згоден з вами; Я насправді не хотів критикувати ваше рішення само по собі , просто хотів попередити випадкового читача, що остаточну відповідь з цього питання, незважаючи на обіцянки C ++ 11, ще не потрібно писати. Я опублікую огляд теми станом на 2015 рік як відповідь на пов'язане питання .
Альберто М

1
Це "найпростіше"? Не могли б ви пояснити, чому явно набагато простіший rand()варіант не є варіантом, і чи це має значення для некритичного використання, як, наприклад, генерування випадкового індексу зведення? Крім того , я повинен турбуватися про будівництво random_device/ mt19937/ uniform_int_distributionв тугий петлі / вбудованої функції? Чи варто віддати перевагу передати їх навколо?
bluenote10

60

Якщо ваш компілятор підтримує C ++ 0x і використовувати його - це варіант для вас, то новий стандартний <random>заголовок, ймовірно, відповідає вашим потребам. Він має високу якістьuniform_int_distribution яка буде приймати мінімальні та максимальні межі (включно за потребою), і ви можете вибрати серед різних генераторів випадкових чисел, щоб підключитися до цього розподілу.

Ось код, який генерує мільйон випадкових ints, рівномірно розподілених у [-57, 365]. Я використав нові <chrono>засоби std , щоб встигнути, оскільки ви згадали про ефективність, для вас головна проблема.

#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;
}

Для мене (2,8 ГГц Intel Core i5) це друкує:

2.10268e + 07 випадкових чисел в секунду.

Ви можете посіяти генератор, передавши int до його конструктора:

    G g(seed);

Якщо згодом виявиться, що intвін не охоплює діапазон, необхідний для вашого розповсюдження, це можна усунути, змінивши uniform_int_distributionподібне (наприклад, до long long):

    typedef std::uniform_int_distribution<long long> D;

Якщо згодом виявиться, що minstd_randгенератор недостатньо якісний, його також можна легко замінити. Наприклад:

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

Маючи окремий контроль над генератором випадкових чисел, і випадкове розподіл може бути досить визвольним.

Я також обчислив (не показано) перші 4 "моменти" цього розподілу (використовуючи minstd_rand) та порівняв їх із теоретичними значеннями , намагаючись кількісно оцінити якість розподілу:

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_Префікс стосується "очікуваного")


3
Ця відповідь може використовувати короткий фрагмент зведеного коду, який показує лише той код, який насправді необхідний для генерації випадкового цілого числа з діапазону.
arekolek

Проблема полегшується тим, що min і max розподілу ніколи не змінюються. Що робити, якщо вам довелося створювати dна кожній ітерації з різними межами? На скільки це сповільнить цикл?
Quant_dev

15

Розділимо проблему на дві частини:

  • Створіть випадкове число nв діапазоні від 0 до (макс-хв).
  • До цього числа додайте хв

Перша частина, очевидно, найважча. Припустимо, що повернене значення rand () є абсолютно рівномірним. Використання модуля додасть упередженості до перших (RAND_MAX + 1) % (max-min+1)чисел. Так що, якщо ми могли б чарівним чином змінити , RAND_MAXщоб RAND_MAX - (RAND_MAX + 1) % (max-min+1), не було б більше не будуть якісь - або упередження.

Виявляється, ми можемо використовувати цю інтуїцію, якщо ми готові дозволити псевдо-недетермінізму в час роботи нашого алгоритму. Щоразу, коли rand () повертає занадто велике число, ми просто запитуємо інше випадкове число, поки не отримаємо таке, яке є достатньо малим.

Час роботи зараз розподілено геометрично , з очікуваним значенням, 1/pде pє ймовірність отримати достатньо невелике число при першій спробі. Оскільки RAND_MAX - (RAND_MAX + 1) % (max-min+1)це завжди менше, ніж (RAND_MAX + 1) / 2ми знаємо це p > 1/2, тому очікувана кількість повторень завжди буде менше двох для будь-якого діапазону. У стандартному процесорі за допомогою цієї методики має бути можливість генерувати десятки мільйонів випадкових чисел менше ніж за секунду.

Редагувати:

Хоча вищезазначене є технічно правильним, відповідь DSimon, ймовірно, корисніша на практиці. Ви не повинні реалізовувати цей матеріал самостійно. Я бачив багато реалізацій вибірки відхилень, і часто буває дуже важко зрозуміти, правильно чи ні.



3
Веселий факт: Джоел Спольський одного разу згадав версію цього питання як приклад того, що StackOverflow добре відповів. Я переглянув відповіді на майданчику з участю вибірки відбраковування в той час і кожен сингл один невірний.
Jørgen Fogh

13

Як щодо Mersenne Twister ? Реалізація поштовху досить проста у використанні та добре перевірена у багатьох реальних програмах. Я сам це використовував у кількох академічних проектах, таких як штучний інтелект та еволюційні алгоритми.

Ось їхній приклад, коли вони виконують просту функцію для прокатки шестигранної плашки:

#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();
}

О, і ось ще декілька сутенерства цього генератора на випадок, якщо ви не впевнені, що вам слід скористатися ним у значній мірі rand():

Мерсенн Твістер - генератор "випадкових чисел", винайдений Макото Мацумото і Такуджі Нісімура; їх веб-сайт включає численні реалізації алгоритму.

По суті, Mersenne Twister - це дуже великий регістр зсуву лінійної зворотної зв'язку. Алгоритм працює на 19 937 бітовому насінні, що зберігається в 624-елементному масиві 32-бітних непідписаних цілих чисел. Значення 2 ^ 19937-1 - прайм Мерсена; техніка маніпулювання насінням базується на старовинному алгоритмі «скручування» - звідси назва «Мерсен Твістер».

Привабливим аспектом Mersenne Twister є його використання бінарних операцій - на відміну від багаторазового множення - для отримання чисел. Алгоритм також має дуже тривалий період і хорошу деталізацію. Це одночасно швидко і ефективно для некриптографічних програм.


1
Твістер Mersenne - хороший генератор, але проблема, з якою він вирішується, залишається незалежно від самого генератора.
Джеррі Труну

Я не хочу використовувати Boost лише для генератора випадкових випадків, тому що (оскільки мій проект є бібліотекою) це означає ввести ще одну залежність до проекту. Я, мабуть, буду змушений використовувати його будь-коли в майбутньому, тож тоді зможу перейти на цей генератор.
Matěj Zábský

1
@Jerry Coffin Яка проблема? Я запропонував це, оскільки він задовольнив усі його вимоги: він швидкий, рівномірний (використовуючи boost::uniform_intдистрибутив), ви можете перетворити діапазони min max у все, що вам подобається, і це піддається насінню.
Афекс

@mzabsky я, мабуть, не дозволив би мені це зупинити, коли мені довелося надсилати свої проекти моїм професорам для подання, я просто включив відповідні файли заголовків підвищення, які я використовував; вам не доведеться упаковувати всю свою бібліотеку збільшення 40 Мб разом із кодом. Звичайно, у вашому випадку це може бути нездійсненно з інших причин, таких як авторське право ...
Aphex

@Aphex Мій проект насправді не є науковим симулятором чи чимось, що потребує дійсно рівномірного розподілу. Я використовував старий генератор протягом 1,5 років без жодних проблем, я помітив упереджене розповсюдження лише тоді, коли мені вперше знадобилося його для отримання чисел із дуже малого діапазону (3 у цьому випадку). Швидкість як і раніше є аргументом для розгляду прискореного рішення. Я перегляну його ліцензію, щоб побачити, чи можу я просто додати кілька потрібних файлів до свого проекту - мені подобається "Оформити замовлення -> F5 -> готовий до використання", як зараз.
Matěj Zábský

11
int RandU(int nMin, int nMax)
{
    return nMin + (int)((double)rand() / (RAND_MAX+1) * (nMax-nMin+1));
}

Це відображення 32768 цілих чисел до (nMax-nMin + 1) цілих чисел. Відображення буде досить хорошим, якщо (nMax-nMin + 1) невеликий (як у вашій вимозі). Однак зауважте, що якщо (nMax-nMin + 1) великий, відображення не працюватиме (наприклад, ви не можете зіставити значення 32768 до 30000 з однаковою ймовірністю). Якщо такі діапазони потрібні - вам слід використовувати 32-бітне або 64-бітове випадкове джерело замість 15-бітових rand () або ігнорувати результати rand (), які знаходяться поза діапазоном.


Незважаючи на свою непопулярність, це також те, що я використовую для своїх ненаукових проектів. Легкий для розуміння (вам не потрібен ступінь математики) і працює адекватно (ніколи не доводилося профілювати якийсь код, використовуючи його). :) У разі великих діапазонів, я думаю, ми могли б об'єднати два значення rand () разом і отримати 30-бітове значення для роботи (якщо припустити RAND_MAX = 0x7fff, тобто 15 випадкових біт)
efotinis

змінити, RAND_MAXщоб (double) RAND_MAXуникнути цілого попередження про переповнення.
алекс

4

Ось об'єктивна версія, яка генерує числа у [low, high]:

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

Якщо ваш діапазон досить малий, немає ніяких причин кешувати праву частину порівняння в doциклі.


ІМО, жодне з представлених рішень там справді не покращилось. Його рішення, що базується на циклі, працює, але, ймовірно, є досить неефективним, особливо для невеликого діапазону, про який говорить ОП. Його рівномірний девіантний розчин насправді взагалі не дає рівномірних відхилень. Найбільше це вид камуфляжу через відсутність рівномірності.
Джеррі Труну

@Jerry: Перевірте нову версію.
Єремія Віллок

Я трохи не впевнений, що правильно працюю. Це може бути, але правильність не здається очевидною, принаймні, для мене.
Джеррі Труну

@Jerry: Ось моє міркування: припустимо, що діапазон призначений [0, h)для простоти. Виклик rand()має RAND_MAX + 1можливі значення повернення; приймаючи rand() % hзгортання (RAND_MAX + 1) / hїх до кожного з hвихідних значень, за винятком того, що (RAND_MAX + 1) / h + 1з них відображаються значення, менші ніж (RAND_MAX + 1) % h(через останній частковий цикл через hвиходи). Тому ми видаляємо (RAND_MAX + 1) % hможливі результати, щоб отримати неупереджений розподіл.
Єремія Віллок

3

Я рекомендую бібліотеку Boost.Random , вона дуже детальна і добре задокументована, дозволяє чітко вказати, який дистрибутив ви хочете, а в некриптографічних сценаріях насправді може перевершити типову реалізацію ранду бібліотеки С.


1

припустимо, що min і max є значеннями int, [і] засоби включають це значення, (і) означає, що не включають це значення, використовуючи вище, щоб отримати потрібне значення, використовуючи c ++ rand ()

довідка: для () [] визначте, відвідайте:

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

для функції rand і srand або визначення RAND_MAX відвідайте:

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

[хв, макс]

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

(хв., макс.)

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

[хв, макс.)

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

(хв, макс)

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

0

У цій нитці вибірки відхилення потоку вже обговорювались, але я хотів запропонувати одну оптимізацію, що ґрунтується на тому, що rand() % 2^somethingне вносить жодних упереджень, як уже згадувалося вище.

Алгоритм дійсно простий:

  • обчисліть найменшу потужність на 2 більше, ніж довжина інтервалу
  • рандомізувати одне число у тому "новому" інтервалі
  • повернути це число, якщо воно менше тривалості початкового інтервалу
    • відкиньте інакше

Ось мій зразок коду:

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
} 

Це добре спрацьовує особливо для невеликих інтервалів, тому що потужність 2 буде "ближче" до реальної довжини інтервалу, і тому кількість промахів буде меншою.

PS
Очевидно, що уникнення рекурсії було б більш ефективним (не потрібно обчислювати над і над стелею журналу ..), але я вважав, що для цього прикладу це можна прочитати більше.


0

Зауважте, що в більшості пропозицій початкове випадкове значення, яке ви отримали від функції rand (), яке, як правило, становить від 0 до RAND_MAX, просто витрачається. Ви створюєте з нього лише одне випадкове число, тоді як є звукова процедура, яка може дати вам більше.

Припустимо, що ви хочете [хв, макс] область цілих випадкових чисел. Починаємо з [0, макс-хв]

Візьміть за основу b = max-min + 1

Почніть з представлення числа, отриманого від rand () в базі b.

Таким чином, ви отримали підлогу (log (b, RAND_MAX)), оскільки кожна цифра в базовій частині b, крім можливо останньої, являє собою випадкове число в діапазоні [0, max-min].

Звичайно, підсумковий зсув до [min, max] простий для кожного випадкового числа r + min.

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

Якщо NUM_DIGIT - це число цифри в базі b, яке можна витягти, і це

NUM_DIGIT = floor(log(b,RAND_MAX))

то вищевикладене є простою реалізацією вилучення NUM_DIGIT випадкових чисел від 0 до b-1 з одного RAND_MAX випадкового числа, що забезпечує b <RAND_MAX.


-1

Формула цього дуже проста, тому спробуйте цей вираз,

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0

2
Вся проблема полягала у використанні ранду C / C ++, який повертає ціле число в діапазоні, визначеному часом виконання. Як показано в цій темі, відображення випадкових цілих чисел від [0, RAND_MAX] до [MIN, MAX] не зовсім просто, якщо ви хочете уникнути руйнування їх статистичних властивостей або продуктивності. Якщо у вас є подвійний діапазон [0, 1], відображення легко.
Matěj Zábský

2
Ваша відповідь неправильна, замість цього слід використовувати модуль:int num = (int) rand() % (max - min) + min;
Хайме Іван Сервантес,

-2

Наступний вираз повинен бути неупередженим, якщо я не помиляюся:

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

Я припускаю, що rand () дає вам випадкове значення в діапазоні від 0,0 до 1,0 НЕ, включаючи 1,0, і що max і min є цілими числами за умови, що min <max.


std::floorповертається double, і нам тут потрібно ціле значення. Я б просто кинув, intа не використовувати std::floor.
musiphil
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.