srand() - зачем называть это только один раз?
Этот вопрос о комментарии в этом вопросе Рекомендуемый способ инициализации srand? Первый комментарий говорит, что srand()
должен вызываться только ОДИН РАЗ в приложении. Почему это так?
7 ответов
Это зависит от того, чего вы пытаетесь достичь.
Рандомизация выполняется как функция, которая имеет начальное значение, а именно начальное число.
Таким образом, для одного и того же начального числа вы всегда получите одну и ту же последовательность значений.
Если вы будете пытаться установить начальное число каждый раз, когда вам нужно случайное значение, а начальное число будет одним и тем же числом, вы всегда получите одно и то же "случайное" значение.
Семя обычно берется из текущего времени, которое является секундами, как в time(NULL)
, так что если вы всегда устанавливаете начальное число перед тем, как взять случайное число, вы получите один и тот же номер, если вы будете вызывать комбинацию srand/rand несколько раз в одну и ту же секунду.
Чтобы избежать этой проблемы, srand устанавливается только один раз для каждого приложения, поскольку сомнительно, что два экземпляра приложения будут инициализированы в одну и ту же секунду, поэтому каждый экземпляр будет иметь различную последовательность случайных чисел.
Однако существует небольшая вероятность того, что вы будете запускать свое приложение (особенно если оно короткое, или инструмент командной строки, или что-то в этом роде) много раз в секунду, тогда вам придется прибегнуть к другому способу выбора. seed (если одна и та же последовательность в разных экземплярах приложения вам не подходит). Но, как я уже сказал, это зависит от контекста использования вашего приложения.
Кроме того, вы можете попытаться увеличить точность до микросекунд (сводя к минимуму вероятность того же затравки), требует (sys/time.h
):
struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);
Случайные числа на самом деле являются псевдослучайными. Семя устанавливается первым, из которого каждый вызов 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
call изменит внутреннее состояние, обновит его и вернет случайное число. Каждый звонок myrand
будет иметь другое next
Значение поэтому метод будет возвращать разные номера при каждом вызове.
Посмотрите на mysrand
реализация; он просто устанавливает начальное значение, которое вы передаете next
, Поэтому, если вы установите next
оценивать то же самое каждый раз перед вызовом rand
он вернет то же случайное значение из-за идентичной формулы, примененной к нему, что нежелательно, так как функция сделана случайной.
Но в зависимости от ваших потребностей вы можете установить начальное значение на определенное значение, чтобы генерировать одну и ту же "случайную последовательность" при каждом запуске, скажем, для какого-то теста или другого.
Краткий ответ: звонок 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 Да, я знаю, в реальной жизни, когда вы покупаете совершенно новую колоду карт, она обычно в порядке, а не в случайном порядке. Чтобы провести аналогию здесь, я представляю, что каждая колода, которую вы покупаете в игровом магазине, имеет, казалось бы, случайный порядок, но точно такой же, казалось бы, случайный порядок, как и любая другая колода карт, которую вы покупаете в том же магазине. Вроде как одинаково перемешанные колоды карт, которые они используют в турнирах по бриджу.]
Причина в том, что srand()
устанавливает начальное состояние генератора случайных чисел, и все значения, которые генерирует генератор, являются "достаточно случайными", если вы сами не касаетесь промежуточного состояния.
Например, вы можете сделать:
int getRandomValue()
{
srand(time(0));
return rand();
}
а затем, если вы вызываете эту функцию несколько раз, чтобы time()
возвращает те же значения в соседних вызовах, которые вы просто получаете сгенерированное значение - это разработано.
Более простое решение для использования srand()
для генерации различных начальных чисел для экземпляров приложения, запускаемых в одну и ту же секунду, как видно
srand(time(NULL)-getpid());
Этот метод делает ваше семя очень близким к случайному, так как невозможно угадать, когда начался ваш поток, и pid также будет другим.
srand seed генератор псевдослучайных чисел. Если вы называете это более одного раза, вы будете повторно заполнять ГСЧ. И если вы вызываете его с тем же аргументом, он перезапустит ту же последовательность.
Чтобы доказать это, если вы делаете что-то простое, как:
#include <cstdlib>
#include <cstdio>
int main() {
for(int i = 0; i != 100; ++i) {
srand(0);
printf("%d\n", rand());
}
}
Вы увидите одно и то же число, напечатанное 100 раз.
1\ Кажется, что каждый раз, когда запускается rand (), он устанавливает новое семя для следующего rand ().
2 \ Если srand () запускается несколько раз, проблема в том, что если два запуска происходят в одну секунду (время (NULL) не меняется), следующий rand () будет таким же, как rand () сразу после предыдущего srand().