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

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


person Lipika Deka    schedule 08.09.2011    source источник
comment
попробуйте, в цикле, вызывая srand, а затем rand   -  person Foo Bah    schedule 08.09.2011
comment
См. Также Обзор бухгалтерского учета Дилберта.   -  person Jonathan Leffler    schedule 03.01.2016


Ответы (7)


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

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

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

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

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

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

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

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

struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);
person Kornelije Petak    schedule 08.09.2011
comment
Боковое примечание: gettimeofday устарело в POSIX 2008. Вместо этого он вводит clock_gettime, который может потребовать связывания с -lrt. Однако, возможно, он еще не доступен на многих платформах. В Linux это нормально. На Mac я думаю, что это еще не доступно. В Windows это, вероятно, никогда не будет доступно. - person Shahbaz; 07.04.2013
comment
t1.tv_usec - это длинное целое число, а srand принимает на входе беззнаковое целое число. (И я просто столкнулся с проблемой, когда это имеет значение.) - person Jiminion; 26.04.2017
comment
Это помогло. Увеличив точность, он избавился от моих дубликатов. Большое спасибо, очень. У меня есть крайний срок доставки, и это спасло меня. - person Beezer; 01.12.2020

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

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

person phoxis    schedule 08.09.2011
comment
Разве вы не имеете в виду (длинное семя без знака) для параметра mysrand ()? - person Jiminion; 26.04.2017
comment
@Jiminion Это фрагмент кода от man srand. Диапазон составляет от 0 до 32767 (при условии RAND_MAX), что намного меньше диапазона long. Переменная состояния next сделана long, поскольку внутреннее умножение и сложение будут превышать диапазон unsigned int. После этого результат масштабируется или изменяется в пределах указанного выше диапазона. Хотя вы можете сделать семя long. - person phoxis; 08.05.2017
comment
Обратите внимание, что стандарт C также включает показанный фрагмент кода. - person Jonathan Leffler; 07.09.2017

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

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

person Steve Summit    schedule 25.10.2016
comment
Великолепное объяснение, Стив. - person DrunkenMaster; 02.07.2019

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

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

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

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

person sharptooth    schedule 08.09.2011

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

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

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

person achoora    schedule 11.01.2016

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

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

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

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

person Foo Bah    schedule 08.09.2011
comment
Речь идет о C, а не о C ++. - person Spikatrix; 17.10.2015

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

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

person imoc    schedule 02.12.2017
comment
Суть в том, что инициализация с srand() несколько раз с одним и тем же начальным значением приведет к идентичным значениям, возвращаемым rand(). - person King Thrushbeard; 02.12.2017