Генерация случайных чисел равномерно по всему диапазону

Мне нужно генерировать случайные числа в указанном интервале, [макс; мин].

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

В настоящее время я создаю как:

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. Есть ли другой метод генерации случайных чисел с равномерным распределением?

20 ответов

Решение

Зачем 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::random_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);

Алгоритм будет переупорядочивать элементы случайным образом с линейной сложностью.

Boost.Random

Другой вариант, если у вас нет доступа к компилятору C++11+, это использовать Boost.Random. Его интерфейс очень похож на интерфейс C++11.

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

Достаточно, чтобы цифры выглядели случайными для типичного человека в спешке, не более.

Посмотрите ответ @ Джеффри для лучших вариантов, или этот ответ для крипто-безопасных случайных чисел.


Как правило, старшие биты показывают лучшее распределение, чем младшие биты, поэтому рекомендуемый способ генерации случайных чисел диапазона для простых целей:

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

Примечание: убедитесь, что RAND_MAX+1 не переполняется (спасибо Деми)!

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

Это также позволяет избежать проблем с нарезкой по модулю, что может еще больше ухудшить ваши показатели.


Не гарантируется, что встроенный генератор случайных чисел будет иметь качество, необходимое для статистического моделирования. Это нормально, что числа "выглядят случайными" для человека, но для серьезного применения вы должны взять что-то лучше - или хотя бы проверить его свойства (равномерное распределение обычно хорошо, но значения имеют тенденцию коррелировать, а последовательность детерминирована). У Кнута отличный (если трудно читаемый) трактат о генераторах случайных чисел, и недавно я обнаружил, что LFSR превосходен и чертовски прост в реализации, учитывая, что его свойства приемлемы для вас.

Я хотел бы дополнить превосходные ответы 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

Boost.Random (документация) - это библиотека, которая вдохновила C++11 <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';
}

Хотя минимальная выборка хорошо работает, реальные программы должны использовать пару улучшений:

  • делать mt19937 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);
}

Это простое, эффективное и аккуратное решение. Единственный недостаток - сборка займет некоторое время - около двух лет, при условии, что C++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. Единственный небольшой недостаток состоит в том, что из-за плохого характера бухгалтерских троллей Дилберта, эта программа фактически порождает неопределенное поведение.

Если RAND_MAX равен 32767, вы можете легко удвоить количество бит.

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

Если вы можете, используйте Boost. Мне повезло с их случайной библиотекой.

uniform_int должен делать то, что вы хотите.

Если вас беспокоит случайность, а не скорость, вам следует использовать безопасный метод генерации случайных чисел. Есть несколько способов сделать это... Самый простой из них - использовать генератор случайных чисел OpenSSL.

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

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

Я не могу обещать, что это ответ, но, пожалуйста, опубликуйте ваше значение RAND_MAX и немного больше информации о вашей среде.

Использование движка Мерсенна Твистер (С++ 11):

      #include <random>    

// Returns a random integer within the range [min, max]
int generateRandomInt(const int min, const int max) {
  static bool is_seeded = false;
  static std::mt19937 generator;

  // Seed once
  if (!is_seeded) {
    std::random_device rd;
    generator.seed(rd());
    is_seeded = true;
  }

  // Use mersenne twister engine to pick a random number
  // within the given range
  std::uniform_int_distribution<int> distribution(min, max);
  return distribution(generator);
}

Это должно обеспечить равномерное распределение по всему диапазону [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::iform_int_distribution.

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

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

Это не код, но эта логика может вам помочь.

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

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

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

Если вы хотите, чтобы числа были равномерно распределены по диапазону, вы должны разбить ваш диапазон на несколько равных частей, которые представляют собой необходимое вам количество баллов. Затем получите случайное число с мин / макс для каждого раздела.

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

Это решение, которое я придумал:

#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. Проверьте это на себе и решите, соответствует ли он вашим потребностям.

Решение от 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)));

Конечно, это не идеальная случайность или однородность, как указывают некоторые другие сообщения, но этого достаточно для большинства случаев.

Это старая тема, но я только сейчас наткнулся на нее. Вот мое мнение:

Как справедливо отмечали другие, MSB, как правило, распределяются более случайным образом, чем LSB, в большинстве ГСЧ. Это означает, что использование по модулю (%) rand() обречено: например, чрезвычайно частый случайный (2) вернет только один младший бит... который чрезвычайно плохо распределен в большинстве ГСЧ. С другой стороны, если вам нужно, чтобы ваш random(N) был очень быстрым (как я: я занимаюсь высокопроизводительными вычислениями и, в частности, сильно рандомизированными ГА), модуль хорош для своей скорости.

Обе вышеупомянутые проблемы могут быть решены путем (1) вычисления ( быстрого ) модуля... из (2) обращенных битов rand() .

По своей природе небольшая выборка случайных чисел не должна быть равномерно распределена. В конце концов, они случайные. Я согласен с тем, что если генератор случайных чисел генерирует числа, которые последовательно представляются сгруппированными, то, вероятно, с этим что-то не так.

Но имейте в виду, что случайность не обязательно одинакова.

Изменить: я добавил "маленький образец", чтобы уточнить.

Минимальная реализация с С++11:

      #include <random>

int randrange (int min, int max) {
    static std::random_device rd; // Static in case init is costly
    return std::uniform_int_distribution {min, max} (rd);
}

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

Предупреждение: не забывайте, что из-за растяжения и возможных ошибок точности (даже если RAND_MAX были достаточно большими), вы сможете генерировать только равномерно распределенные "ячейки", а не все числа в [min, max].


@Solution: Bigrand

Предупреждение: обратите внимание, что это удваивает биты, но все равно не сможет генерировать все числа в вашем диапазоне в целом, т. Е. Не обязательно верно, что BigRand() будет генерировать все числа между в своем диапазоне.


Информация: Ваш подход (по модулю) "в порядке", если диапазон rand() превышает диапазон интервалов, а rand() "равномерный". Ошибка не более первых максимальных и минимальных чисел равна 1/(RAND_MAX +1).

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

Конечно, следующий код не даст вам случайных чисел, а будет псевдослучайным числом. Используйте следующий код

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

Например:

int myRand = QUICK_RAND(10, 20);

Вы должны позвонить

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

иначе числа не будут случайными.

Я только что нашел это в Интернете. Это должно работать:

DWORD random = ((min) + rand()/(RAND_MAX + 1.0) * ((max) - (min) + 1));
Другие вопросы по тегам