Srand() - зачем вызывать его только один раз?

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

Ответ 1

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

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

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

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

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

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

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

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

struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);

Ответ 2

Случайные числа на самом деле являются псевдослучайными. Сначала устанавливается семя, из которого каждый вызов rand получает случайное число и изменяет внутреннее состояние, и это новое состояние используется в следующем вызове rand для получения другого номера. Поскольку определенная формула используется для генерации этих "случайных чисел", поэтому установка определенного значения семени после каждого вызова 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, оно будет возвращать одно и то же случайное значение из-за идентичной формулы, примененной к нему, что нежелательно, так как функция сделана случайной.

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

Ответ 3

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

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

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

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

Ответ 4

Короткий ответ: вызов 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, который будет разумно случайным, так что вы начнете с другого случайное место в большой колоде каждый раз, когда запускается ваша программа.

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

Ответ 5

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

Чтобы доказать это, выполните следующие действия:

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

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

Ответ 6

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

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

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

Ответ 7

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

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