Самая фундаментальная проблема вашего тестового приложения заключается в том, что вы вызываете srand
один раз, затем rand
один раз вызываете и выходите.
Весь смысл srand
функции состоит в том, чтобы инициализировать последовательность псевдослучайных чисел случайным начальным числом .
Это означает , что если вы передаете то же значение для srand
двух различных приложений (с той же srand
/ rand
реализацией) , то вы получите точно такую же последовательность из rand()
считанных значений после этого в обоих приложениях.
Однако в вашем примере приложения псевдослучайная последовательность состоит только из одного элемента - первого элемента псевдослучайной последовательности, сгенерированной из начального числа, равного текущему времени second
точности. Что вы ожидаете увидеть на выходе?
Очевидно, что когда вы запускаете приложение в одну и ту же секунду - вы используете одно и то же начальное значение - таким образом, ваш результат, конечно же, тот же (как уже упоминал Мартин Йорк в комментарии к вопросу).
На самом деле вы должны звонить srand(seed)
один раз, а затем звонить rand()
много раз и анализировать эту последовательность - она должна выглядеть случайной.
РЕДАКТИРОВАТЬ:
О, я понял. Видимо словесного описания недостаточно (может языковой барьер что ли ... :)).
ОК. Пример старомодного кода C, основанный на тех же 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;
while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
{ }
printf( "%d,\t%d\n", n, n % 6 + 1 );
}
return 0;
}
^^^ ЭТА последовательность из одного запуска программы должна выглядеть случайной.
ОБРАТИТЕ ВНИМАНИЕ, что я не рекомендую использовать rand
/ srand
functions в производстве по причинам, объясненным ниже, и я абсолютно не рекомендую использовать функцию time
в качестве случайного начального числа по причинам, которые IMO уже должны быть довольно очевидными. Они хороши для образовательных целей и иногда для иллюстрации сути, но для любого серьезного использования они в основном бесполезны.
РЕДАКТИРОВАТЬ2:
При использовании стандартной библиотеки C или C ++ важно понимать, что на данный момент нет ни одной стандартной функции или класса, производящего действительно случайные данные окончательно (гарантированные стандартом). Единственный стандартный инструмент, который подходит к этой проблеме, - это std :: random_device, который, к сожалению, до сих пор не дает гарантий фактической случайности.
В зависимости от характера приложения вы должны сначала решить, действительно ли вам нужны действительно случайные (непредсказуемые) данные. Примечательным случаем, когда вам, безусловно, нужна истинная случайность, является информационная безопасность - например, создание симметричных ключей, асимметричных закрытых ключей, значений соли, токенов безопасности и т. Д.
Однако случайные числа уровня безопасности - это отдельная отрасль, заслуживающая отдельной статьи.
В большинстве случаев достаточно генератора псевдослучайных чисел - например, для научного моделирования или игр. В некоторых случаях даже требуется согласованно определенная псевдослучайная последовательность - например, в играх вы можете создавать точно такие же карты во время выполнения, чтобы избежать хранения большого количества данных.
Исходный вопрос и повторяющееся множество идентичных / похожих вопросов (и даже множество ошибочных «ответов» на них) указывают на то, что в первую очередь важно отличать случайные числа от псевдослучайных чисел И понимать, что такое последовательность псевдослучайных чисел в во-первых, И понять, что генераторы псевдослучайных чисел НЕ используются так же, как вы могли бы использовать истинные генераторы случайных чисел.
Интуитивно, когда вы запрашиваете случайное число - возвращаемый результат не должен зависеть от ранее возвращенных значений и не должен зависеть от того, запрашивал ли кто-либо что-либо раньше, и не должен зависеть в какой момент и с помощью какого процесса, на каком компьютере и от какого генератора и в какая галактика запрашивалась. Это то, что в конце концов означает слово «случайный» - непредсказуемость и независимость от чего-либо - иначе это уже не случайность, верно? С такой интуицией вполне естественно искать в сети какие-то магические заклинания, которые можно использовать, чтобы получить такое случайное число в любом возможном контексте.
^^^ ТАКОЕ интуитивное ожидание ОЧЕНЬ НЕПРАВИЛЬНО и вредно во всех случаях, связанных с генераторами псевдослучайных чисел, несмотря на то, что они разумны для истинных случайных чисел.
Хотя осмысленное понятие «случайное число» существует (своего рода) - такого понятия, как «псевдослучайное число» не существует. Псевдо-генератор случайных чисел на самом деле производит псевдослучайных чисел последовательности .
Псевдослучайная последовательность на самом деле всегда детерминирована (предопределена своим алгоритмом и исходными параметрами), т.е. в ней фактически нет ничего случайного.
Когда эксперты говорят о качестве ГПСЧ, они на самом деле говорят о статистических свойствах сгенерированной последовательности (и ее примечательных подпоследовательностей). Например, если вы объединяете два высококачественных PRNG, используя их оба по очереди - вы можете получить плохую результирующую последовательность - несмотря на то, что они генерируют хорошие последовательности каждая по отдельности (эти две хорошие последовательности могут просто коррелировать друг с другом и, таким образом, плохо сочетаться).
Конкретно rand()
/ srand(s)
пара функций обеспечивают особую для каждого процесса , не поточно-(!) Псевдослучайных чисел последовательности генерируются с помощью алгоритма реализации. Функция rand()
производит значения в диапазоне [0, RAND_MAX]
.
Цитата из стандарта C11 (ISO / IEC 9899: 2011):
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 устаревшего кода C выше:
#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);
for( unsigned long j = 0; j < 100500; ++j )
{
std::mt19937::result_type n;
while( ( n = gen() ) > std::mt19937::max() -
( std::mt19937::max() - 5 )%6 )
{ }
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;
}