srand() - почему звоните только один раз?

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

7 ответов


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

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

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

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

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

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

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

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

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

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

обычно мы используем time (NULL) возвращаемое значение секунд при инициализации начального значения. Скажи srand (time (NULL)); это в цикле. Тогда цикл может повторяться более одного раза в одну секунду, поэтому количество раз цикл повторяется внутри цикла за секунду 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 он вернет то же случайное значение из-за примененной к нему одинаковой Формулы, что нежелательно, так как функция быть случайным.

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


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

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

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

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


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

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


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

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

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

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


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

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

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


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

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