Проблемы с раздачей генератора псевдослучайных чисел более одного раза?

Я видел довольно много рекомендаций, чтобы не загружать генераторы псевдослучайных чисел более одного раза за выполнение, но они никогда не сопровождались подробным объяснением. Конечно, легко понять, почему следующий пример (C / C ++) не является хорошей идеей:

int get_rand() {
  srand(time(NULL));
  return rand();
}

поскольку вызов get_rand несколько раз в секунду дает повторяющиеся результаты.

Но разве следующий пример не будет приемлемым решением?

MyRand.h

#ifndef MY_RAND_H
#define MY_RAND_H

class MyRand
{
  public:
    MyRand();
    int get_rand() const;
  private:
    static unsigned int seed_base;
};

#endif

MyRand.cpp

#include <ctime>
#include <cstdlib>
#include "MyRand.h"

unsigned int MyRand::seed_base = static_cast<unsigned int>(time(NULL));

MyRand::MyRand()
{
  srand(seed_base++);
}

int MyRand::get_rand() const
{
  return rand();
}

main.cpp

#include <iostream>
#include "MyRand.h"

int main(int argc, char *argv[]) 
{
  for (int i = 0; i < 100; i++) 
  {
    MyRand r;
    std::cout << r.get_rand() << " ";
  }
}

т.е. даже несмотря на то, что конструктор MyRand: s вызывается несколько раз в быстрой последовательности, каждый вызов srand имеет другой параметр. Очевидно, что это не потокобезопасно, но опять же, rand тоже.


person kvadevack    schedule 10.06.2009    source источник
comment
Я мог бы добавить, что вся цель этого упражнения - облегчить бремя вызова srand от клиента MyRand, где MyRand может моделировать кристалл. Но с другой стороны, если мы таким же образом построим колесо фортуны, подбрасывание монет и т. Д., Мы получим много семян.   -  person kvadevack    schedule 10.06.2009


Ответы (4)


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

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

Например, популярный простой ГСЧ - это линейный конгруэнтный генератор. Числа генерируются так:

X[n+1] = (a X[n] + c) mod m

В этом случае X [n + 1] является и результатом, и новым внутренним состоянием. Если вы каждый раз запускаете генератор, как вы предлагаете выше, вы получите следующую последовательность:

{(ab + c) mod m, (a(b+1) + c) mod m, (a(b+2) + c) mod m, ...}

где b - ваш seed_base. Это вовсе не выглядит случайным.

person Jay Conrod    schedule 10.06.2009
comment
Мой пример main.cpp немного преувеличен с целью продемонстрировать отсутствие недостатка первого примера. Создание экземпляра нового объекта для каждого вызова get_rand приведет к ситуации, которую вы описали выше, но это просто бесполезное программирование. Если предположить, что количество MyRand экземпляров мало по сравнению с get_rand вызовами, все выглядит немного лучше. - person kvadevack; 10.06.2009

Если ваше семя предсказуемо, а оно здесь, поскольку вы просто увеличиваете его, вывод rand () также будет предсказуемым.

Это действительно зависит от того, почему вы хотите сгенерировать случайные числа, и насколько «случайное» является для вас приемлемым случайным числом. В вашем примере он может избегать дублирования в быстрой последовательности, и этого может быть достаточно для вас. В конце концов, важно то, что он работает.

Почти на каждой платформе есть лучший способ генерировать случайные числа, чем rand ().

person i_am_jorf    schedule 10.06.2009

Что ж, это дополнительная обработка, которая не требуется.

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

Я бы не подумал, что ваш метод более случайный, чем этот.

person Steve Wortham    schedule 10.06.2009

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

Посев более одного раза просто не дает дополнительных преимуществ, поскольку этого должно быть достаточно (в зависимости от приложения). Если вам действительно нужно «больше» случайных чисел, существует множество методов генерации случайных чисел. Один случай, который я могу придумать, - это генерировать случайные числа потокобезопасным способом.

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

person Sam    schedule 10.06.2009