Генерация случайного целого числа из диапазона

158

Мне нужна функция, которая генерирует случайное целое число в заданном диапазоне (включая значения границ). У меня нет необоснованных требований к качеству / случайности, у меня есть четыре требования:

  • Мне нужно, чтобы это было быстро. Мой проект должен генерировать миллионы (а иногда даже десятки миллионов) случайных чисел, и моя текущая функция генератора оказалась узким местом.
  • Мне нужно, чтобы он был достаточно равномерным (использование rand () прекрасно).
  • диапазон минимальных и максимальных значений может быть от <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.

Не могли бы вы предложить лучшую формулу? (или даже целая функция генератора псевдослучайных чисел)

Матей Забский
источник
1
Смотрите: stackoverflow.com/questions/2254498/…
Джерри Коффин
3
@ Билл МаГриф: да. У него та же проблема. Упрощенная версия: как можно равномерно распределить 10 конфет между 3 детьми (не ломая конфет)? Ответ в том, что вы не можете - вы должны дать каждому ребенку по три, а десятому никому не давать.
Джерри Коффин
5
Вы смотрели на Boost.Random ?
Фред Нурк
3
Посмотрите статью Эндрю Кенига «Простая проблема, которая почти никогда не решается правильно»: drdobbs.com/blog/archives/2010/11/a_simple_proble.html
Джин Бушуев,
1
@Gene Bushuyev: И Эндрю, и я уже давно об этом говорим. Смотрите: 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.
Матей Забский
3
Всегда возвращает максимальное значение. Я что-то здесь упускаю? : |
Рохан-Патель
15
rand()в C ++ следует считать вредными, есть гораздо лучшие способы получить что-то равномерно распределенное и фактически случайное.
Mgetz
1
Действительно ли он возвращает правильное число в диапазоне 100% времени? Я нашел здесь другой ответ на stackoverflow, который использует рекурсию, чтобы сделать это «правильным образом»: stackoverflow.com/a/6852396/623622
Czarek Tomczak
2
Поскольку это ответ с большим количеством голосов (чем хотелось бы), который кажется надежным источником информации для многих новых читателей, я думаю, что очень важно упомянуть качество и потенциальную опасность этого решения, поэтому я внес изменения.
плазмацел
297

Самый простой (и, следовательно, лучший) ответ 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>средства стандартизации для определения времени, так как вы упомянули, что производительность является серьезной проблемой для вас.

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

Для меня (Intel Core i5 2,8 ГГц) это распечатывает:

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
В этом ответе может использоваться фрагмент краткого сводного кода, который показывает только тот код, который действительно необходим для генерации случайного целого числа из диапазона.
ареколек
Проблема облегчается тем, что минимальное и максимальное распределения никогда не меняются. Что если бы вам пришлось создавать dна каждой итерации разные границы? Насколько это замедлит цикл?
Quant_Dev
16

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

  • Генерация случайного числа 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, вероятно, более полезен на практике. Вы не должны реализовывать это самостоятельно. Я видел много реализаций выборки отклонения, и часто очень трудно увидеть, правильно это или нет.

Йорген Фог
источник
Для полноты: это выборка отклонения .
etarion
3
Забавный факт: Джоэл Спольски однажды упомянул версию этого вопроса в качестве примера того, на что StackOverflow хорошо ответил. Я просмотрел ответы на площадке с участием выборки отбраковки в то время и каждый сингл один неверный.
Йорген Фог,
13

Как насчет Мерсена Твистера ? Реализация boost довольно проста в использовании и хорошо протестирована во многих реальных приложениях. Я сам использовал его в нескольких научных проектах, таких как искусственный интеллект и эволюционные алгоритмы.

Вот их пример, где они делают простую функцию, чтобы бросить шестигранный кубик:

#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 - это генератор «случайных чисел», изобретенный Макото Мацумото и Такудзи Нисимура; их сайт включает в себя многочисленные реализации алгоритма.

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

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

Aphex
источник
1
Твистер Мерсенна - хороший генератор, но проблема, с которой он имеет дело, остается, независимо от самого генератора.
Джерри Гроб
Я не хочу использовать Boost только для генератора случайных чисел, потому что (поскольку мой проект является библиотекой), это означает введение другой зависимости в проект. Я, вероятно, буду вынужден использовать его в любом случае в будущем, так что тогда я могу переключиться на этот генератор.
Матей Забский
1
@ Джерри Гроб Какая проблема? Я предложил его, потому что он удовлетворял всем его требованиям: он быстрый, равномерный (с использованием boost::uniform_intраспределения), вы можете преобразовать минимальные максимальные диапазоны во все, что вам нравится, и это можно посеять.
Aphex
@mzabsky Я, вероятно, не позволил бы этому помешать мне, когда я должен был отослать свои проекты моим профессорам для представления, я просто включил соответствующие файлы заголовка повышения, которые я использовал; вам не нужно упаковывать всю библиотеку надстроек 40 Мб вместе с вашим кодом. Конечно, в вашем случае это может оказаться невозможным по другим причинам, таким как авторское право ...
Aphex
@Aphex Мой проект не является научным симулятором или чем-то, что нуждается в действительно равномерном распределении. Я использовал старый генератор в течение 1,5 лет без каких-либо проблем, я заметил, что смещение распределено только тогда, когда мне сначала понадобилось генерировать числа из очень небольшого диапазона (в данном случае 3). Тем не менее, скорость все еще является аргументом для решения проблемы повышения. Я посмотрю на его лицензию, чтобы увидеть, могу ли я просто добавить несколько необходимых файлов в свой проект - мне нравится «Оформить заказ -> F5 -> готов к использованию», как сейчас.
Матей Забский
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цикле.

Иеремия Уиллкок
источник
ИМО, ни одно из представленных там решений действительно не сильно улучшилось. Его основанное на петле решение работает, но, вероятно, будет довольно неэффективно, особенно для небольшого диапазона, как обсуждается в OP. Его унифицированное отклоняющееся решение на самом деле не дает одинаковых отклонений вообще. В большинстве случаев это маскирует отсутствие единообразия.
Джерри Гроб
@ Джерри: Пожалуйста, проверьте новую версию.
Иеремия Уиллкок
Я немного не уверен, что это работает правильно. Возможно, но правильность не кажется очевидной, по крайней мере, мне.
Джерри Коффин
@ Джерри: Вот мои рассуждения: предположим, что диапазон [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 , она очень детализирована и хорошо документирована, она позволяет вам явно указать, какой дистрибутив вы хотите, и в не криптографических сценариях может фактически превзойти типичную реализацию ранда библиотеки C.

DSimon
источник
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
Очевидно, что избегать рекурсии было бы более эффективно (не нужно вычислять снова и снова потолок бревна ...), но я подумал, что это будет более читабельно для этого примера.

Pado
источник
0

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

Предположим, что вы хотите [min, max] область целых случайных чисел. Начнем с [0, max-min]

Взять базу 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.

alex.peter
источник
-1

Формула для этого очень проста, поэтому попробуйте это выражение,

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0
Сохаил xIN3N
источник
2
Вся проблема заключалась в использовании ранда C / C ++, который возвращает целое число в диапазоне, указанном во время выполнения. Как показано в этом потоке, отображение случайных целых чисел из [0, RAND_MAX] в [MIN, MAX] не совсем просто, если вы хотите избежать разрушения их статистических свойств или производительности. Если у вас есть удвоения в диапазоне [0, 1], отображение легко.
Матей Забский
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.

Moritz
источник
std::floorвозвращает double, и нам нужно целочисленное значение здесь. Я бы просто использовал intвместо того, чтобы использовать std::floor.
Musiphil