srand () - навіщо це називати лише один раз?


78

Це питання стосується коментаря до цього питання Рекомендований спосіб ініціалізації srand? Перший коментар говорить, що його srand()слід називати ТІЛЬКИ РАЗУ в додатку. Чому це так?


спробуйте, у циклі, викликаючи srand, а потім rand
Foo Bah

Відповіді:


111

Це залежить від того, чого ви намагаєтесь досягти.

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

Отже, для одного і того ж насіння ви завжди отримаєте однакову послідовність значень.

Якщо ви намагаєтеся встановити насіння щоразу, коли вам потрібне випадкове значення, і насіння має однакове число, ви завжди отримаєте однакове "випадкове" значення.

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

Щоб уникнути цієї проблеми, srand встановлюється лише один раз для кожної програми, оскільки сумнівно, що два екземпляри програми будуть ініціалізовані в одну секунду, тому кожен екземпляр матиме іншу послідовність випадкових чисел.

Однак існує невелика ймовірність того, що ви запустите свою програму (особливо якщо вона коротка, або інструмент командного рядка, або щось подібне) багато разів за секунду, тоді вам доведеться вдатися до іншого способу вибору seed (за винятком випадків, коли однакова послідовність у різних екземплярах програми є нормальною для вас). Але, як я вже сказав, це залежить від контексту використання вашого додатка.

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


3
Примітка: gettimeofdayзастаріла в POSIX 2008. Натомість вона вводить, clock_gettimeщо може вимагати зв’язку з -lrt. Однак, можливо, він ще не доступний на багатьох платформах. У Linux це нормально. На Mac я думаю, що це ще не доступно. У Windows це, мабуть, ніколи не буде доступним.
Шахбаз,

1
t1.tv_usec - це довгий int, і srand приймає як вхід непідписаний int. (І я просто зіткнувся з проблемою, де це має значення.)
Jiminion

Це зробило трюк. Збільшивши точність, він позбувся моїх дублікатів. Дякую дуже багато. У мене є термін, щоб доставити, і це врятувало моє дерріє.
Beezer

24

Випадкові числа насправді є псевдовипадковими. Спочатку встановлюється насіння, з якого кожен виклик randотримує випадкове число, і змінює внутрішній стан, і цей новий стан використовується в наступному randвиклику, щоб отримати інший номер. Оскільки для генерування цих "випадкових чисел" використовується певна формула, тому встановлення певного значення затравки після кожного виклику randбуде повертати той самий номер із виклику. Наприклад srand (1234); rand ();, поверне те саме значення. Ініціалізація одного разу початкового стану із початковим значенням генерує достатньо випадкових чисел, оскільки ви не встановлюєте внутрішній стан srand, таким чином роблячи числа більш імовірними, щоб бути випадковими.

Як правило, ми використовуємо time (NULL)значення секунд, що повертається, при ініціалізації початкового значення. Скажімо, srand (time (NULL));це в циклі. Тоді цикл може повторюватися більше одного разу за одну секунду, тому кількість повторень циклу всередині циклу за другий randвиклик у циклі повертає те саме "випадкове число", що не є бажаним. Ініціалізація його один раз під час запуску програми встановить насіння один раз, і кожен раз, коли randбуде викликаний, генерується нове число і змінюється внутрішній стан, тому наступний виклик randповертає номер, який є досить випадковим.

Наприклад, цей код з http://linux.die.net/man/3/rand :

Внутрішній стан nextоголошено глобальним. Кожен myrandвиклик змінить внутрішній стан і оновить його, і поверне випадковий номер. Кожен дзвінок myrandбуде мати різнеnext значення, тому метод повертатиме різні номери кожного виклику.

Подивіться на mysrandреалізацію; він просто встановлює початкове значення, на яке ви переходите next. Тому, якщо ви встановлюєте nextзначення однаково кожного разу перед тим, як викликати randйого, воно поверне одне і те ж випадкове значення через однакову формулу, застосовану до нього, що не бажано, оскільки функція зроблена випадковою.

Але залежно від ваших потреб ви можете встановити насіння для певного значення, щоб генерувати однакову "випадкову послідовність" кожного циклу, скажімо для деяких еталонів чи інших.


Ви не маєте на увазі (довге насіння без підпису) для параметра mysrand ()?
Jiminion

@Jiminion Це фрагмент коду з man srand. Діапазон від 0 до 32767 (припускаючи RAND_MAX), що значно менше, ніж longдіапазон. Змінна стану nextробиться, longоскільки внутрішнє множення і додавання перевищує діапазон an unsigned int. Після цього результат масштабується або модифікується в межах зазначеного вище діапазону. Хоча ви можете зробити насіння long.
фоксис

1
Зверніть увагу, що стандарт C включає також показаний фрагмент коду.
Джонатан Леффлер,

12

Коротка відповідь: покликання 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 Так, я знаю, у реальному житті, коли ви купуєте нову колоду карт, як правило, це в порядку, а не у випадковому порядку. Щоб тут працювала аналогія, я уявляю, що кожна колода, яку ви купуєте в ігровому магазині, знаходиться у, здавалося б, випадковому порядку, але точно такий же, здавалося б, випадковий, як і кожна інша колода карт, яку ви купуєте в тому ж магазині. Начебто як ідентично перемішані колоди карт, які вони використовують у турнірах на бриджі.]


Чудове пояснення Стів.
DrunkenMaster 02

8

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

Наприклад, ви можете зробити:

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


3

Простіше рішення для використання srand()для генерації різних насіння для екземплярів програми, що виконуються в одну секунду, як показано.

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


2

srand створює генератор псевдовипадкових чисел. Якщо ви зателефонуєте йому більше одного разу, ви повторно завантажите RNG. І якщо ви викликаєте його з тим самим аргументом, він перезапустить ту ж послідовність.

Щоб довести це, якщо ви зробите щось таке просте, як:

ви побачите той самий номер, надрукований 100 разів.


2
Питання стосується С, а не С ++.
Spikatrix

0

1 \ Здається, щоразу, коли запускається rand (), воно встановлюватиме нове насіння для наступного rand ().

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


Головне полягає в тому, що ініціалізація srand()кількома разами з одним і тим самим насінням призведе до ідентичних значень, повернутих rand().
King Thrushbeard,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.