Найголовніша проблема вашої заявки на тестування полягає в тому, що ви телефонуєте srand
один раз, а потім телефонуєте rand
один раз і виходите.
Вся суть srand
функції полягає в ініціалізації послідовності псевдовипадкових чисел з випадковим початком.
Це означає , що якщо ви передаєте те ж значення для srand
двох різних додатків (з тієї ж srand
/ rand
реалізацією) , то ви отримаєте таку саму послідовність з rand()
лічених значень після цього в обох додатках.
Однак у вашому прикладі програми псевдовипадкова послідовність складається лише з одного елемента - першого елемента псевдовипадкової послідовності, сформованого з насіння, рівного поточному часу second
точності. Що ви очікуєте побачити на виході?
Очевидно, коли вам трапляється запускати програму в одну і ту ж секунду - ви використовуєте одне і те ж значення насіння - таким чином, ваш результат, звичайно, такий же (як уже згадував Мартін Йорк у коментарі до питання).
Насправді вам слід зателефонувати srand(seed)
один раз, а потім зателефонувати rand()
багато разів та проаналізувати цю послідовність - це має виглядати випадково.
Редагувати:
О, я розумію. Мабуть, словесного опису недостатньо (можливо, мовний бар'єр чи щось ... :)).
ГАРАЗД. Приклад старомодного коду С на основі тих же srand()/rand()/time()
функцій, що і в запитанні:
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main(void)
{
unsigned long j;
srand( (unsigned)time(NULL) );
for( j = 0; j < 100500; ++j )
{
int n;
/* skip rand() readings that would make n%6 non-uniformly distributed
(assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
{ /* bad value retrieved so get next one */ }
printf( "%d,\t%d\n", n, n % 6 + 1 );
}
return 0;
}
^^^ ТАЯ послідовність з одного запуску програми повинна виглядати випадково.
EDIT2:
Використовуючи стандартну бібліотеку C або C ++, важливо розуміти, що на сьогоднішній день не існує єдиної стандартної функції або класу, який би остаточно давав фактичні випадкові дані (це гарантується стандартом). Єдиним стандартним інструментом, який підходить до цієї проблеми, є std :: random_device, який, на жаль, все ще не дає гарантій фактичної випадковості.
Залежно від характеру програми, слід спочатку вирішити, чи справді вам потрібні справді випадкові (непередбачувані) дані. Примітним випадком, коли вам справді потрібна справжня випадковість, є захист інформації - наприклад, генерування симетричних ключів, асиметричних приватних ключів, значень солі, жетонів безпеки тощо.
Однак випадкові числа класів безпеки - це окрема галузь, яка вартує окремої статті.
У більшості випадків достатньо генератора псевдовипадкових чисел - наприклад, для наукових симуляцій чи ігор. У деяких випадках навіть потрібна послідовно визначена псевдовипадкова послідовність - наприклад, в іграх ви можете генерувати точно такі ж карти під час виконання, щоб уникнути зберігання великої кількості даних.
Первісне запитання та повторюваність безлічі однакових / подібних питань (і навіть багато помилкових "відповідей" на них) вказують на те, що перш за все важливо відрізнити випадкові числа від псевдовипадкових чисел І зрозуміти, що таке послідовність псевдовипадкових чисел у перше місце ТА зрозуміти, що генератори псевдовипадкових чисел НЕ використовуються так само, як ви могли використовувати справжні генератори випадкових чисел.
Інтуїтивно, коли ви запитуєте випадкове число - результат, що повертається, не повинен залежати від раніше повернених значень і не повинен залежати, якщо хтось щось запитував раніше, і не повинен залежати в який момент і яким процесом, на якому комп'ютері і на якому генераторі та в якому яку галактику вона запитувала. Ось що слово "випадковий" означає все-таки - бути непередбачуваним і незалежним ні від чого - інакше воно вже не випадкове, правда? З цією інтуїцією цілком природно шукати в Інтернеті певні магічні заклинання, щоб отримати таке випадкове число в будь-якому можливому контексті.
^^^ ТАКІ інтуїтивні очікування ДУЖЕ НЕПРАВИЛЬНІ та шкідливі у всіх випадках із псевдо-випадковими генераторами чисел - незважаючи на те, що розумні для справжніх випадкових чисел.
Хоча змістовне поняття "випадкове число" існує - немає такого поняття, як "псевдовипадкове число". Псевдо-генератор випадкових чисел на насправді виробляє псевдовипадкових чисел послідовності .
Коли експерти говорять про якість PRNG, вони насправді говорять про статистичні властивості генерованої послідовності (та її помітних підрядів). Наприклад, якщо ви поєднуєте два високоякісні PRNG, використовуючи їх обидва по черзі - ви можете створити погану послідовність результатів, - незважаючи на те, що вони генерують хороші послідовності кожна окремо (ці дві хороші послідовності можуть просто співвідноситися між собою і таким чином погано поєднуватись).
Псевдовипадкова послідовність насправді завжди детермінована (заздалегідь визначена її алгоритмом та початковими параметрами), тобто насправді в цьому немає нічого випадкового.
Конкретно rand()
/ srand(s)
пара функцій забезпечує сингулярну псевдовипадкову псевдовипадкову послідовність чисел (!) Для кожного процесу, що генерується за допомогою алгоритму, визначеного реалізацією. Функція rand()
створює значення в діапазоні [0, RAND_MAX]
.
Цитата від стандарту C11:
srand
Функція використовує аргумент в якості затравки для нової послідовності псевдовипадкових чисел , які будуть повернуті при наступних викликах rand
. Якщо
srand
потім викликається однакове значення насіння, послідовність псевдовипадкових чисел повторюється. Якщо rand
виклик перед тим, srand
як були здійснені будь-які дзвінки , формується та сама послідовність, що і коли srand
вперше викликається із початковим значенням 1.
Багато людей , розумно очікувати , що rand()
буде виробляти послідовність напівзалежними рівномірно розподілених чисел в діапазоні 0
до RAND_MAX
. Ну, це, безумовно, повинно (інакше це марно), але, на жаль, не тільки стандарт цього не вимагає - є навіть явна відмова від відповідальності, яка стверджує, що "немає гарантій якості виробленої випадкової послідовності" . У деяких історичних випадках rand
/ srand
реалізація була дуже поганої якості на самому справі. Навіть незважаючи на те, що в сучасних реалізаціях це, швидше за все, досить добре - але довіра порушена і відновити її непросто. Окрім того, що його безпечний характер робить його безпечним використання у багатопотокових програмах складним та обмеженим (все-таки можливо - ви можете просто використовувати їх з однієї спеціальної теми).
Новий шаблон класу std :: mersenne_twister_engine <> (та його зручність typedefs - std::mt19937
/ std::mt19937_64
з хорошою комбінацією параметрів шаблону) забезпечує генератор псевдовипадкових чисел на об'єкт, визначений у стандарті C ++ 11. З однаковими параметрами шаблону та однаковими параметрами ініціалізації різні об'єкти будуть генерувати абсолютно однакову послідовність виводу на один об'єкт на будь-якому комп'ютері будь-якого додатка, побудованого зі стандартною бібліотекою, що відповідає C ++ 11. Перевагою цього класу є його передбачувано висока якість вихідної послідовності та повна узгодженість між реалізаціями.
Також є більше двигунів PRNG, визначених у стандарті C ++ 11 - std :: linear_congruential_engine <> (історично використовується як srand/rand
алгоритм справедливої якості в деяких реалізаціях стандартної бібліотеки C) та std :: subtract_with_carry_engine <> . Вони також генерують повністю визначені параметри, що залежать від виводу послідовностей на об'єкт.
Приклад заміни сучасного C ++ 11 для застарілого коду С вище:
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
// seed value is designed specifically to make initialization
// parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
// different across executions of application
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
for( unsigned long j = 0; j < 100500; ++j )
/* ^^^Yes. Generating single pseudo-random number makes no sense
even if you use std::mersenne_twister_engine instead of rand()
and even when your seed quality is much better than time(NULL) */
{
std::mt19937::result_type n;
// reject readings that would make n%6 non-uniformly distributed
while( ( n = gen() ) > std::mt19937::max() -
( std::mt19937::max() - 5 )%6 )
{ /* bad value retrieved so get next one */ }
std::cout << n << '\t' << n % 6 + 1 << '\n';
}
return 0;
}
Версія попереднього коду, що використовує std :: uniform_int_distribution <>
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
std::uniform_int_distribution<unsigned> distrib(1, 6);
for( unsigned long j = 0; j < 100500; ++j )
{
std::cout << distrib(gen) << ' ';
}
std::cout << '\n';
return 0;
}