Створюйте випадкові числа рівномірно по всьому діапазону


93

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

Крім того, випадкові числа повинні бути рівномірно розподілені по інтервалу, не розташовані в певній точці.

В даний час я створюю як:

for(int i=0; i<6; i++)
{
    DWORD random = rand()%(max-min+1) + min;
}

З моїх тестів випадкові числа генеруються навколо однієї точки.

Example
min = 3604607;
max = 7654607;

Створені випадкові числа:

3631594
3609293
3630000
3628441
3636376
3621404

З відповідей нижче: Добре, RAND_MAX - 32767. Я на платформі C ++ Windows. Чи існує інший метод генерування випадкових чисел з рівномірним розподілом?



1
Я навіть не підозрював, що C ++ rand()є уніфікованим. Якою бібліотекою ви користуєтесь? cstdlib.h«S rand()не є рівномірним: cplusplus.com/reference/cstdlib/rand
Майк Уоррен

3
Ні, rand () є уніфікованим (за винятком деяких ранніх реалізацій глюків). те, що є нерівномірним, - це використання оператора модуля '%' для обмеження діапазону. Див stackoverflow.com/questions/2999075 / ... правильного рішення, або якщо у вас є «arc4random_uniform» доступний , то ви можете використовувати його безпосередньо , а також.
Джон Міхам

@ Alien01: Чи не могли б Ви змінити прийняту відповідь на відповідь "Взуття" ("Чому rand - погана ідея" тощо)? Моя відповідь насправді застаріла, і кожного разу, коли я отримую за неї голос, я відчуваю, що хтось біжить неправильним проходом.
peterchen

Приємний довідковий документ про випадковість в C ++ 11.
Пупсік

Відповіді:


153

Чому randце погана ідея

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

C ++ 11 та генерація в діапазоні

З C ++ 11 зросли кілька інших варіантів. Один з яких відповідає вашим вимогам, для генерації випадкового числа в діапазоні, досить добре: std::uniform_int_distribution. Ось приклад:

const int range_from  = 0;
const int range_to    = 10;
std::random_device                  rand_dev;
std::mt19937                        generator(rand_dev());
std::uniform_int_distribution<int>  distr(range_from, range_to);

std::cout << distr(generator) << '\n';

І ось наведений приклад.

Інші генератори випадкових випадків

<random>Тема пропонує безліч інших генераторів випадкових чисел з різними видами розподілів , включаючи Бернуллі, Пуассона і нормальне.

Як я можу перетасувати контейнер?

Стандарт передбачає std::shuffle, що можна використовувати наступним чином:

std::vector<int> vec = {4, 8, 15, 16, 23, 42};

std::random_device random_dev;
std::mt19937       generator(random_dev());

std::shuffle(vec.begin(), vec.end(), generator);

Алгоритм буде впорядковувати елементи випадково, з лінійною складністю.

Підвищення. Випадкове

Ще однією альтернативою, якщо у вас немає доступу до компілятора C ++ 11 +, є використання Boost.Random . Його інтерфейс дуже схожий на інтерфейс C ++ 11.


22
Зверніть увагу на цю відповідь, оскільки вона набагато сучасніша.
gsamaras

Це правильна відповідь. Дякую! Тим не менше, я хотів би бачити більш поглиблений опис кожного кроку цього коду. Наприклад, що таке mt19937тип?
Аполлон,

@Apollo У документації йдеться про "32-розрядний Mersenne Twister від Мацумото та Нішимури, 1998". Я припускаю, що це алгоритм генерації псевдовипадкових чисел.
Взуття

@Shoe, для заданого діапазону, він генерує числа в порядку 1 9 6 2 8 7 1 4 7 7. Ви як це рандомізуєте кожного разу, коли ми запускаємо програму?

1
@Richard Яка альтернатива?
Взуття

59

[редагувати] Попередження: Не використовувати rand()для статистики, моделювання, криптографії чи чогось серйозного.

Це досить добре, щоб цифри виглядали випадковими для типової людини, яка поспішає, не більше.

Див. Відповідь @ Jefffrey, щоб отримати кращі варіанти, або цю відповідь щодо криптозахищених випадкових чисел.


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

((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

Примітка : переконайтесь, що RAND_MAX + 1 не переповнюється (спасибі Демі)!

Ділення генерує випадкове число в інтервалі [0, 1); "розтягніть" це до необхідного діапазону. Тільки коли max-min + 1 наближається до RAND_MAX, вам потрібна функція "BigRand ()", яку опублікував Марк Ренсом.

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


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


4
BigRand може дати кращі результати, навіть якщо бажаний діапазон не перевищує RAND_MAX. Поміркуйте, коли RAND_MAX дорівнює 32767, і ви хочете 32767 можливих значень - два із цих 32768 випадкових чисел (включаючи нуль) збираються зіставити з одним і тим же виходом, і це буде вдвічі частіше, ніж інші. Навряд чи ідеальна випадкова властивість!
Марк Ренсом

7
(RAND_MAX + 1) - погана ідея. Це може перекинутися і дати вам негативне значення. Краще зробити щось на зразок: ((подвійний) RAND_MAX) + 1,0
Демі

3
@peterchen: Я думаю, ти неправильно зрозумів, що говорив демі. Вона мала на увазі таке: ( rand() / ((double)RAND_MAX+1)) * (max-min+1) + min просто переведіть перетворення вдвічі і уникніть проблеми.
Mooing Duck

3
Крім того, це просто змінює розподіл від нижчих значень 32767 в діапазоні до рівномірно розподілених 32767 значень у діапазоні, а решта 4017233 значень ніколи не будуть обрані цим алгоритмом.
Mooing Duck

1
Дана відповідь вимикається на 1. Правильним рівнянням є: ((подвійний) rand () / (RAND_MAX + 1.0)) * (max-min) + min "max-min + 1" використовується, коли використовується% not * . Ви побачите, чому, коли ви робите min = 0, max = 1. Чи міг би peterchen або @ peter-mortensen змінити це.
davepc

17

Я хотів би доповнити чудові відповіді Angry Shoe та Peterchen коротким оглядом сучасного стану в 2015 році:

Деякі хороші варіанти

randutils

randutilsБібліотека (презентація) є цікавою новинкою, пропонуючи простий інтерфейс і (що оголосив) надійні випадкові можливості. Він має недоліки, що додає залежність від вашого проекту, і, будучи новим, він не був широко перевірений. У будь-якому випадку, будучи безкоштовним (ліцензія MIT) та лише заголовком, я думаю, що варто спробувати.

Мінімальний зразок: рулон

#include <iostream>
#include "randutils.hpp"
int main() {
    randutils::mt19937_rng rng;
    std::cout << rng.uniform(1,6) << "\n";
}

Навіть якщо хтось не цікавиться бібліотекою, веб-сайт ( http://www.pcg-random.org/ ) містить багато цікавих статей про тему генерації випадкових чисел взагалі та бібліотеку C ++ зокрема.

Підвищення. Випадкове

Boost.Random (Документація) є бібліотекою , яка надихнула C++11«S <random>, з яким розділяє більшу частину інтерфейсу. Хоча теоретично також є зовнішньою залежністю, Boostна сьогоднішній день має статус "квазістандартної" бібліотеки, і її Randomмодуль можна розглядати як класичний вибір для якісного генерації випадкових чисел. Він має дві переваги щодо C++11рішення:

  • він більш портативний, просто потрібна підтримка компілятора для C ++ 03
  • він random_deviceвикористовує специфічні для системи методи, щоб запропонувати посів хорошої якості

Єдиним невеликим недоліком є ​​те, що пропозиція модуля random_deviceне лише для заголовка, її потрібно скомпілювати та зв’язати boost_random.

Мінімальний зразок: рулон

#include <iostream>
#include <boost/random.hpp>
#include <boost/nondet_random.hpp>

int main() {
    boost::random::random_device                  rand_dev;
    boost::random::mt19937                        generator(rand_dev());
    boost::random::uniform_int_distribution<>     distr(1, 6);

    std::cout << distr(generator) << '\n';
}

Хоча мінімальна вибірка добре працює, реальні програми повинні використовувати пару вдосконалень:

  • make mt19937a thread_local: генератор досить пухкий (> 2 КБ) і краще не виділяти його в стек
  • насіння mt19937з більш ніж одним цілим числом: Mersenne Twister має великий стан і може скористатися більшою ентропією під час ініціалізації

Деякі не дуже вдалі варіанти вибору

Бібліотека C ++ 11

Попри те, що <random>бібліотека є найбільш ідіоматичним рішенням, бібліотека не пропонує багато в обмін на складність інтерфейсу навіть для основних потреб. Недолік полягає у std::random_deviceтому, що Стандарт не вимагає жодної мінімальної якості для своїх результатів (до тих пір, поки entropy()повертається 0), і, починаючи з 2015 року, MinGW (не найуживаніший компілятор, але навряд чи езотеричний вибір) завжди буде друкувати 4на мінімальній вибірці.

Мінімальний зразок: рулон

#include <iostream>
#include <random>
int main() {
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<int>  distr(1, 6);

    std::cout << distr(generator) << '\n';
}

Якщо реалізація не гнила, це рішення має бути еквівалентним Boost, і застосовуються ті самі пропозиції.

Рішення Годо

Мінімальний зразок: рулон

#include <iostream>
#include <random>

int main() {
    std::cout << std::randint(1,6);
}

Це просте, ефективне та акуратне рішення. Тільки дефект, компіляція займе деякий час - близько двох років, якщо С ++ 17 буде випущено вчасно і експериментальна randintфункція затверджена в новому Стандарті. Можливо, до того часу також покращаться гарантії щодо якості посіву.

Гірше-це-краще рішення

Мінімальний зразок: рулон

#include <cstdlib>
#include <ctime>
#include <iostream>

int main() {
    std::srand(std::time(nullptr));
    std::cout << (std::rand() % 6 + 1);
}

Старе рішення C вважається шкідливим і з поважних причин (див. Інші відповіді тут або цей детальний аналіз ). Тим не менше, він має свої переваги: ​​є простим, портативним, швидким і чесним, в тому сенсі, що відомо, що випадкові числа, які отримуєш, навряд чи пристойні, і тому не виникає спокуси використовувати їх для серйозних цілей.

Рішення бухгалтерського троля

Мінімальний зразок: рулон

#include <iostream>

int main() {
    std::cout << 9;   // http://dilbert.com/strip/2001-10-25
}

Незважаючи на те, що 9 є дещо незвичним результатом для звичайного рулону, слід захоплюватися чудовим поєднанням хороших якостей у цьому рішенні, яке вдається бути найшвидшим, найпростішим, найбільш зручним та керованим. Замінивши 9 на 4, ви отримуєте ідеальний генератор для будь-якого подземелля і помирання драконів, уникаючи при цьому символічно навантажених значень 1, 2 і 3. Єдиний невеликий недолік полягає в тому, що через поганий характер бухгалтерських тролів Ділберта, ця програма насправді породжує невизначену поведінку.


Зараз randutilsбібліотека називається PCG.
tay10r

11

Якщо RAND_MAX32767, ви можете легко подвоїти кількість бітів.

int BigRand()
{
    assert(INT_MAX/(RAND_MAX+1) > RAND_MAX);
    return rand() * (RAND_MAX+1) + rand();
}

Я не думаю, що це працює. Генератори псевдовипадкових чисел, як правило, детерміновані. Наприклад, якщо перший randдзвінок повертається, 0x1234а другий 0x5678, тоді ви отримуєте 0x12345678. Це єдине число, яке ви можете отримати, яке починається 0x1234, оскільки наступне число буде завжди 0x5678. Ви отримуєте 32-розрядні результати, але у вас є лише 32768 можливих чисел.
user694733

@ user694733 хороший генератор випадкових чисел має період, який перевищує кількість виходів, які він може генерувати, тому 0x1234 не завжди буде супроводжуватися 0x5678.
Марк Ренсом

9

Якщо у вас є можливість, використовуйте Boost . Мені пощастило з їхньою випадковою бібліотекою .

uniform_int повинен робити те, що ти хочеш.


Я провів деяку роботу над uniform_int за допомогою merseinne twister, і, на жаль, для певних діапазонів значення, які повертає uniform_int, не такі одноманітні, як я очікував. Наприклад, uniform_int <> (0, 3) має тенденцію виробляти більше 0, ніж 1 або 2
ScaryAardvark

@ScaryAardvark, що звучить як погана реалізація того uniform_intчасу. Генерувати неупереджений результат досить просто, тут було кілька питань, які демонструють метод.
Марк Ренсом

@ Марк Викуп. Так, я цілком погодився б.
ScaryAardvark

8

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

Ви також можете написати свій власний, використовуючи алгоритм шифрування (наприклад, AES ). Вибираючи насіння та IV, а потім безперервно перешифровуючи вихідні дані функції шифрування. Використовувати OpenSSL простіше, але менш по-чоловічому.


Я не можу користуватися будь-якою сторонньою бібліотекою? Я обмежений лише C ++.
і

Потім пройдіть чоловічий шлях, застосуйте AES або інший алгоритм шифрування.
SoapBox

2
RC4 є тривіальним для кодування і досить випадковим для всіх практичних цілей (крім WEP, але це не зовсім вина RC4). Я це маю на увазі, це неймовірно тривіальний код. Мовляв, 20 рядків чи близько того. Запис у Вікіпедії містить псевдокод.
Steve Jessop

4
Чому ви не можете використовувати сторонній код? Якщо це питання домашнього завдання, ви повинні сказати це, тому що багато людей воліють давати корисні підказки, а не надавати цілісні рішення у цьому випадку. Якщо це не домашнє завдання, ідіть, киньте хлопця, який каже: "немає коду третьої сторони", бо він дебіл.
DevSolar

Більш пряме посилання на функцію OpenSSL rand () docs: openssl.org/docs/crypto/rand.html#
DevSolar 02

5

Вам слід звернути увагу на RAND_MAXсвій компілятор / середовище. Я думаю, ви побачите ці результати, якщо rand()створюєте випадкове 16-бітове число. (ви, мабуть, припускаєте, що це буде 32-розрядне число).

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


3

Перевірте що RAND_MAX є у вашій системі - я здогадуюсь, що це всього 16 біт, і ваш діапазон занадто великий для цього.

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


Гаразд, RAND_MAX - 32767. Я перебуваю на платформі Windows C ++.
і

2

Це не код, але ця логіка може вам допомогти.

static double rnd(void)
{
   return (1.0 / (RAND_MAX + 1.0) * ((double)(rand())) );
}

static void InitBetterRnd(unsigned int seed)
{
    register int i;
    srand( seed );
    for( i = 0; i < POOLSIZE; i++){
        pool[i] = rnd();
    }
}

 // This function returns a number between 0 and 1
 static double rnd0_1(void)
 {
    static int i = POOLSIZE-1;
    double r;

    i = (int)(POOLSIZE*pool[i]);
    r = pool[i];
    pool[i] = rnd();
    return (r);
}

2

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

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


1

Це повинно забезпечити рівномірний розподіл по діапазону [low, high)без використання поплавків, якщо загальний діапазон менше RAND_MAX.

uint32_t rand_range_low(uint32_t low, uint32_t high)
{
    uint32_t val;
    // only for 0 < range <= RAND_MAX
    assert(low < high);
    assert(high - low <= RAND_MAX);

    uint32_t range = high-low;
    uint32_t scale = RAND_MAX/range;
    do {
        val = rand();
    } while (val >= scale * range); // since scale is truncated, pick a new val until it's lower than scale*range
    return val/scale + low;
}

а для значень більше RAND_MAX потрібно щось на зразок

uint32_t rand_range(uint32_t low, uint32_t high)
{
    assert(high>low);
    uint32_t val;
    uint32_t range = high-low;
    if (range < RAND_MAX)
        return rand_range_low(low, high);
    uint32_t scale = range/RAND_MAX;
    do {
        val = rand() + rand_range(0, scale) * RAND_MAX; // scale the initial range in RAND_MAX steps, then add an offset to get a uniform interval
    } while (val >= range);
    return val + low;
}

Приблизно це робить std :: uniform_int_distribution.


0

За своєю природою невелика вибірка випадкових чисел не повинна бути рівномірно розподіленою. Зрештою, вони випадкові. Я згоден з тим, що якщо генератор випадкових чисел генерує числа, які послідовно видаються згрупованими, то, мабуть, з ним щось не так.

Але майте на увазі, що випадковість не обов'язково є однорідною.

Редагувати: я додав "невеликий зразок" для уточнення.


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

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

Клуге має рацію. Рівномірний розподіл у невеликій вибірці вказує на те, що вибірка точно не є випадковою.
Білл Ящірка

1
Білл, це не вказує на таке. Маленькі вибірки в основному безглузді, але якщо RNG повинен бути рівномірним, а вихід однорідним, чому це гірше, ніж неоднорідний малий зразок?
Ден Дайер,

2
Значні розподіли в будь-якому випадку свідчать про невипадковість: я думаю, Білл просто означає, що 6 рівномірно розташованих результатів також будуть підозрілими. У OP 6 значень лежать у діапазоні 32k / 4M, або <1% від бажаного діапазону. Ймовірність того, що це помилково позитивний результат, занадто мала, щоб сперечатися.
Steve Jessop

0

Рішення, дане man 3 rand для числа від 1 до 10 включно, є:

j = 1 + (int) (10.0 * (rand() / (RAND_MAX + 1.0)));

У вашому випадку це буде:

j = min + (int) ((max-min+1) * (rand() / (RAND_MAX + 1.0)));

Звичайно, це не ідеальна випадковість або одноманітність, на що вказують деякі інші повідомлення, але цього достатньо для більшості випадків.


1
Це просто переставляє розподіл, щоб він виглядав більш рівномірним, але насправді він не є навіть навіть для великих діапазонів (наприклад, у випадку з ОП)
Mooing Duck

0

@Рішення ((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

Попередження : Не забувайте через розтягування та можливі помилки точності (навіть якщо RAND_MAX були досить великими), ви зможете генерувати лише рівномірно розподілені "смітники", а не всі цифри в [хв, макс.].


@ Рішення: Бігранд

Попередження : Зверніть увагу, що це подвоює біти, але все одно не зможе генерувати всіх чисел у вашому діапазоні в цілому, тобто, це не обов'язково вірно, що BigRand () буде генерувати всі числа між своїм діапазоном.


Інформація : Ваш підхід (за модулем) "чудовий", якщо діапазон rand () перевищує діапазон інтервалу, а rand () "рівномірний". Помилка щонайбільше для перших максимально-мінімальних чисел становить 1 / (RAND_MAX +1).

Крім того, я пропоную також перейти на новий випадковий пакет e у C ++ 11, який пропонує кращі та більше різновидів реалізацій, ніж rand ().


0

Це рішення, яке я придумав:

#include "<stdlib.h>"

int32_t RandomRange(int32_t min, int32_t max) {
    return (rand() * (max - min + 1) / (RAND_MAX + 1)) + min;
}

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

Це робить кілька припущень. По-перше, передбачається, що RAND_MAX * (max - min + 1)він завжди впишеться в int32_t. Якщо RAND_MAXдорівнює 32767 і використовуються 32-бітові обчислення int, максимальний діапазон, який ви можете мати, - 32767. Якщо ваша реалізація має набагато більший RAND_MAX, ви можете подолати це, використовуючи int64_tдля обчислення більше ціле число (наприклад ). По-друге, якщо int64_tвикористовується, але RAND_MAXвсе ще становить 32767, на діапазонах більше ніжRAND_MAX ви почнете отримувати "дірки" у можливих вихідних числах. Це, мабуть, найбільша проблема будь-якого рішення, отриманого від масштабування rand().

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


-1

Звичайно, наступний код надасть вам не випадкові числа, а псевдовипадкові числа. Використовуйте наступний код

#define QUICK_RAND(m,n) m + ( std::rand() % ( (n) - (m) + 1 ) )

Наприклад:

int myRand = QUICK_RAND(10, 20);

Ви повинні зателефонувати

srand(time(0));  // Initialize random number generator.

інакше цифри не будуть майже випадковими.


1
Питання полягає в рівномірному розподілі. Запропоноване рішення не забезпечить рівномірного розподілу. Стандартна бібліотека C ++ має засоби для генерації псевдовипадкових чисел . Вони дійсно забезпечують рівномірний розподіл, якщо цього вимагають.
IIСпецифічно

-3

Я щойно знайшов це в Інтернеті. Це має працювати:

DWORD random = ((min) + rand()/(RAND_MAX + 1.0) * ((max) - (min) + 1));

Будь ласка, поясніть, для чого вони вам потрібні, існує маса алгоритмів для PRNG. Крім того, було б простіше, якби ви редагували своє головне запитання, а не публікували відповіді.
peterchen

Це найкраще для мене працює ... Я можу отримати краще розподілені випадкові числа за цією формулою ..
і

4
Якщо ваш діапазон перевищує RAND_MAX, результати можуть бути НЕ рівномірним. Тобто в діапазоні є значення, які не будуть представлені незалежно від того, скільки разів у виклику вашої функції.
dmckee --- кошеня екс-модератора

4
Крім того, якщо max і min обидва не підписані int, і min дорівнює 0, а max дорівнює MAX_UINT, тоді ((max) - (min) +1) буде 0, а результат завжди буде 0. Слідкуйте за переповненням цієї математики! Як зазначає dmckee, це поширює розподіл на діапазон призначення, але не гарантує більше ніж унікальні значення RAND_MAX.
jesup
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.