Ответ 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
и таким образом сделать движок очень легко заменяемым.