srand () - зачем называть это только один раз? - PullRequest
70 голосов
/ 08 сентября 2011

Этот вопрос касается комментария к этому вопросу Рекомендуемый способ инициализации srand? Первый комментарий говорит, что srand() следует вызывать только ОДИН РАЗ в приложении.Почему это так?

Ответы [ 7 ]

95 голосов
/ 08 сентября 2011

Это зависит от того, чего вы пытаетесь достичь.

Рандомизация выполняется как функция, имеющая начальное значение, а именно начальное число .

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

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

Семя обычно берется из текущего времени, которое является секундами, как в time(NULL), поэтому, если вы всегда устанавливаете семя перед тем, как взять случайное число, вы получите то же число, что ипока вы вызываете комбинацию srand / rand несколько раз в одну и ту же секунду .

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

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

Кроме того, вы можете попытаться увеличить точность до микросекунд (минимизируя вероятность того же начального числа), требует (sys/time.h):

struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);
22 голосов
/ 08 сентября 2011

Случайные числа на самом деле являются псевдослучайными.Сначала устанавливается начальное число, из которого каждый вызов rand получает случайное число и модифицирует внутреннее состояние, и это новое состояние используется в следующем вызове rand для получения другого номера.Поскольку для генерации этих «случайных чисел» используется определенная формула, установка определенного значения seed после каждого вызова на rand будет возвращать один и тот же номер из вызова.Например, srand (1234); rand (); вернет то же значение.Инициализация после начального состояния с помощью начального значения сгенерирует достаточно случайных чисел, поскольку вы не устанавливаете внутреннее состояние с помощью srand, что делает числа более вероятными случайными.

Обычно мы используем time (NULL)возвращаемое значение секунд при инициализации начального значения.Скажем, srand (time (NULL)); в цикле.Тогда цикл может повторяться более одного раза в секунду, поэтому количество циклов, повторяемых циклом внутри цикла во втором вызове rand в цикле, будет возвращать одно и то же «случайное число», что нежелательно.Однократная инициализация при запуске программы будет устанавливать начальное значение один раз, и каждый раз, когда вызывается rand, генерируется новый номер и изменяется внутреннее состояние, поэтому следующий вызов rand возвращает число, которое является достаточно случайным.

Например, этот код из http://linux.die.net/man/3/rand:

static unsigned long next = 1;
/* RAND_MAX assumed to be 32767 */
int myrand(void) {
    next = next * 1103515245 + 12345;
    return((unsigned)(next/65536) % 32768);
}
void mysrand(unsigned seed) {
    next = seed;
}

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

Посмотрите на реализацию mysrand;он просто устанавливает начальное значение, которое вы передаете next.Поэтому, если вы устанавливаете значение next одинаково каждый раз перед вызовом rand, оно будет возвращать одно и то же случайное значение, поскольку к нему применена одинаковая формула, что нежелательно, поскольку функция сделана случайной.

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

8 голосов
/ 26 октября 2016

Краткий ответ: вызов srand() - это , а не , как "бросание кубика" для генератора случайных чисел.И это не похоже на перетасовку колоды карт.Во всяком случае, это больше похоже на простую колоду карт.

Думайте об этом так.rand() раздает из большой колоды карт, и каждый раз, когда вы ее коллируете, все, что нужно сделать, это выбрать следующую карту сверху колоды, дать вам ценность и вернуть эту карту в конец колоды.(Да, это означает, что «случайная» последовательность будет повторяться через некоторое время. Однако это очень большая колода * : обычно 4 294 967 296 карт.)

Более того, каждый раз, когда ваша программа запускается,Совершенно новая колода карт покупается в игровом магазине, и каждая новая колода карт всегда имеет одинаковую последовательность.Так что, если вы не сделаете что-то особенное, каждый раз, когда ваша программа запускается, она будет возвращать точно такие же «случайные» числа обратно из rand().

Теперь вы можете сказать: «Хорошо, так как мне перетасоватьколода?»И ответ (по крайней мере, что касается rand и srand), нет способа перетасовать колоду.

Так что же делает srand?Исходя из аналогии, которую я здесь строю, звонить по номеру srand(n) - все равно, что сказать: «обрежьте колоду n карт сверху».Но подождите, еще одна вещь: на самом деле возьмите другую совершенно новую колоду и вырежьте ее n карты сверху .

Так что если вы позвоните srand(n), rand(),srand(n), rand(), ..., с одним и тем же n каждый раз, вы не просто получите не очень случайную последовательность, вы на самом деле получите одно и то же число обратно из rand() каждый раз,(Не обязательно тот же номер, который вы передали srand, но один и тот же номер обратно от rand снова и снова.)

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

[PS Да, я знаю, в реальной жизни, когда вы покупаете совершенно новую колоду карт, она обычно в порядке, а не в случайном порядке.Чтобы провести аналогию здесь, я представляю, что каждая колода, которую вы покупаете в игровом магазине, имеет, казалось бы, случайный порядок, но точно такой же, казалось бы, случайный порядок, как и любая другая колода карт, которую вы покупаете в том же магазине.Вроде как одинаково перемешанные колоды карт, которые они используют в турнирах по бриджу.]

7 голосов
/ 08 сентября 2011

Причина в том, что srand() устанавливает начальное состояние генератора случайных чисел, и все значения, которые генерирует генератор, являются "достаточно случайными", если вы сами не касаетесь состояния между ними.

Например, вы можете сделать:

int getRandomValue()
{
    srand(time(0));
    return rand();
}

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

2 голосов
/ 11 января 2016

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

srand(time(NULL)-getpid());

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

2 голосов
/ 08 сентября 2011

и семена генератора псевдослучайных чисел.Если вы называете это более одного раза, вы будете повторно заполнять ГСЧ.И если вы вызываете его с тем же аргументом, он перезапустит ту же последовательность.

Чтобы доказать это, если вы сделаете что-то простое, например:

#include <cstdlib>
#include <cstdio>
int main() {
for(int i = 0; i != 100; ++i) {
        srand(0);
        printf("%d\n", rand());
    }
}

, вы увидите то же число, напечатанное100 раз.

0 голосов
/ 02 декабря 2017

1 \ Кажется, что каждый раз, когда запускается rand (), он устанавливает новое семя для следующего rand ().

2 \ Если srand () запускается несколько раз, проблема в том, что если два запуска происходят в одну секунду (время (NULL) не меняется), следующий rand () будет таким же, как и rand () right после предыдущего srand ().

...