Це питання стосується коментаря до цього питання
Рекомендований спосіб ініціалізації srand? Перший коментар говорить, що його srand()
слід називати ТІЛЬКИ РАЗУ в додатку. Чому це так?
Це питання стосується коментаря до цього питання
Рекомендований спосіб ініціалізації srand? Перший коментар говорить, що його srand()
слід називати ТІЛЬКИ РАЗУ в додатку. Чому це так?
Відповіді:
Це залежить від того, чого ви намагаєтесь досягти.
Рандомізація виконується як функція, яка має початкове значення, а саме насіння .
Отже, для одного і того ж насіння ви завжди отримаєте однакову послідовність значень.
Якщо ви намагаєтеся встановити насіння щоразу, коли вам потрібне випадкове значення, і насіння має однакове число, ви завжди отримаєте однакове "випадкове" значення.
Насіння зазвичай береться з поточного часу, тобто секунд, як і в time(NULL)
, тому, якщо ви завжди встановлюєте насіння перед тим, як взяти випадкове число, ви отримаєте той самий номер, доки ви кілька разів викликаєте комбінацію srand / rand у тієї ж секунди .
Щоб уникнути цієї проблеми, srand встановлюється лише один раз для кожної програми, оскільки сумнівно, що два екземпляри програми будуть ініціалізовані в одну секунду, тому кожен екземпляр матиме іншу послідовність випадкових чисел.
Однак існує невелика ймовірність того, що ви запустите свою програму (особливо якщо вона коротка, або інструмент командного рядка, або щось подібне) багато разів за секунду, тоді вам доведеться вдатися до іншого способу вибору seed (за винятком випадків, коли однакова послідовність у різних екземплярах програми є нормальною для вас). Але, як я вже сказав, це залежить від контексту використання вашого додатка.
Крім того, ви можете спробувати збільшити точність до мікросекунд (мінімізуючи шанс того самого насіння), вимагає ( sys/time.h
):
struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);
gettimeofday
застаріла в POSIX 2008. Натомість вона вводить, clock_gettime
що може вимагати зв’язку з -lrt
. Однак, можливо, він ще не доступний на багатьох платформах. У Linux це нормально. На Mac я думаю, що це ще не доступно. У Windows це, мабуть, ніколи не буде доступним.
Випадкові числа насправді є псевдовипадковими. Спочатку встановлюється насіння, з якого кожен виклик 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
його, воно поверне одне і те ж випадкове значення через однакову формулу, застосовану до нього, що не бажано, оскільки функція зроблена випадковою.
Але залежно від ваших потреб ви можете встановити насіння для певного значення, щоб генерувати однакову "випадкову послідовність" кожного циклу, скажімо для деяких еталонів чи інших.
man srand
. Діапазон від 0 до 32767 (припускаючи RAND_MAX), що значно менше, ніж long
діапазон. Змінна стану next
робиться, long
оскільки внутрішнє множення і додавання перевищує діапазон an unsigned int
. Після цього результат масштабується або модифікується в межах зазначеного вище діапазону. Хоча ви можете зробити насіння long
.
Коротка відповідь: покликання 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()
дійсно найкраще, що ви можете зробити.
[PS Так, я знаю, у реальному житті, коли ви купуєте нову колоду карт, як правило, це в порядку, а не у випадковому порядку. Щоб тут працювала аналогія, я уявляю, що кожна колода, яку ви купуєте в ігровому магазині, знаходиться у, здавалося б, випадковому порядку, але точно такий же, здавалося б, випадковий, як і кожна інша колода карт, яку ви купуєте в тому ж магазині. Начебто як ідентично перемішані колоди карт, які вони використовують у турнірах на бриджі.]
Причина полягає в тому, що srand()
встановлюється початковий стан генератора випадкових випадків, і всі значення, які виробляє генератор, є "достатньо випадковими", якщо ви самі не торкаєтеся стану між ними.
Наприклад, ви можете зробити:
int getRandomValue()
{
srand(time(0));
return rand();
}
а потім, якщо ви неодноразово викликаєте цю функцію, яка time()
повертає однакові значення в сусідніх викликах, ви просто отримуєте одне і те ж значення - це за проектом.
Простіше рішення для використання srand()
для генерації різних насіння для екземплярів програми, що виконуються в одну секунду, як показано.
srand(time(NULL)-getpid());
Цей метод робить ваше насіння дуже наближеним до випадкового, оскільки немає можливості здогадатися, в який час запустилася ваша нитка, і pid також буде іншим.
srand створює генератор псевдовипадкових чисел. Якщо ви зателефонуєте йому більше одного разу, ви повторно завантажите RNG. І якщо ви викликаєте його з тим самим аргументом, він перезапустить ту ж послідовність.
Щоб довести це, якщо ви зробите щось таке просте, як:
#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 ().
srand()
кількома разами з одним і тим самим насінням призведе до ідентичних значень, повернутих rand()
.