Генерация случайных чисел в С++ 11: как генерировать, как это работает?

Недавно я наткнулся на новый способ генерирования случайных чисел в С++ 11, но не смог переварить статьи, которые я читал об этом (что это за механизм, математический термин типа распределения, "где все производимые целые числа одинаково вероятны").

Так может кто-нибудь, пожалуйста, объясните

  • кто они такие?
  • что они имеют в виду?
  • как генерировать?
  • как они работают?
  • так далее

Вы можете назвать все это в одном FAQ о генерации случайных чисел.

Ответ 1

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

Почему "одинаково вероятно"

Предположим, у вас есть простой генератор случайных чисел, который генерирует числа 0, 1,..., 10 каждое с равной вероятностью (представьте это как классический rand()). Теперь вам нужно случайное число в диапазоне 0, 1, 2, каждое с равной вероятностью. Ваша реакция коленного рефлекса будет заключаться в том, чтобы взять rand() % 3. Но подождите, остатки 0 и 1 встречаются чаще, чем остаток 2, так что это не правильно!

Вот почему нам нужны правильные распределения, которые берут источник однородных случайных целых чисел и превращают их в желаемое распределение, как, например, Uniform[0,2] в примере. Лучше всего оставить это в хорошей библиотеке!

Двигатели

Таким образом, в основе всей случайности лежит хороший генератор псевдослучайных чисел, который генерирует последовательность чисел, которые равномерно распределены по определенному интервалу и которые в идеале имеют очень большой период. Стандартная реализация rand() не всегда самая лучшая, и поэтому хорошо иметь выбор. Линейно-конгруэнтный и твистер Мерсенна являются двумя хорошими вариантами (LG также часто используется rand()); опять же, хорошо, чтобы библиотека справилась с этим.

Как это устроено

Легко: сначала настройте двигатель и начните его. Начальное число полностью определяет всю последовательность "случайных" чисел, поэтому a) каждый раз используйте другое (например, взятое из /dev/urandom) и b) сохраняйте начальное число, если вы хотите воссоздать последовательность случайных выборов.

#include <random>

typedef std::mt19937 MyRNG;  // the Mersenne Twister with a popular choice of parameters
uint32_t seed_val;           // populate somehow

MyRNG rng;                   // e.g. keep one global instance (per thread)

void initialize()
{
  rng.seed(seed_val);
}

Теперь мы можем создавать дистрибутивы:

std::uniform_int_distribution<uint32_t> uint_dist;         // by default range [0, MAX]
std::uniform_int_distribution<uint32_t> uint_dist10(0,10); // range [0,10]
std::normal_distribution<double> normal_dist(mean, stddeviation);  // N(mean, stddeviation)

... И использовать движок для создания случайных чисел!

while (true)
{
  std::cout << uint_dist(rng) << " "
            << uint_dist10(rng) << " "
            << normal_dist(rng) << std::endl;

}

совпадение

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

Разное

  • Интересная статья о случайном TR1 по кодегуру.
  • В Википедии есть хорошее резюме (спасибо, @Justin).
  • В принципе, каждый движок должен result_type typedef для result_type, который является правильным целочисленным типом, который следует использовать для начального числа. Я думаю, что однажды у меня была ошибочная реализация, которая вынудила меня принудительно заставить seed для std::mt19937 в uint32_t на x64, в конце концов это должно быть исправлено, и вы можете сказать MyRNG::result_type seed_val и таким образом сделать движок очень легко заменяемым.

Ответ 2

Генератор случайных чисел - это уравнение, которое, учитывая число, даст вам новое число. Обычно вы либо предоставляете первое число, либо вытягиваете его из системного времени.

Каждый раз, когда вы запрашиваете новый номер, он использует предыдущий номер для выполнения уравнения.

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

  • 1:1%
  • 2: 80%
  • 3: 5%
  • 4: 5%
  • 5: 9%

2 генерируется FAR чаще, чем любое другое число, поэтому его, скорее всего, производят, чем другие числа. Если бы все номера были одинаковыми, у вас было бы 20% шанс получить каждый номер каждый раз. Чтобы сказать это по-другому, вышеупомянутое распределение очень неравномерно, потому что 2 предпочитают. Распределение со всеми 20% было бы ровным.

Как правило, если вы хотите получить истинное случайное число, вы будете извлекать данные из чего-то вроде погоды или какого-либо другого естественного источника, а не генератора случайных чисел.