Кратко:
Вы используете time.h
, чтобы получить начальное число, которое является начальным случайным числом.Затем C выполняет несколько операций над этим числом, чтобы получить следующее случайное число, затем выполняет операции над этим номером, чтобы получить следующее, затем ... вы получите изображение.
rand()
может затронуть все возможные целые числа.К счастью, он не будет предпочитать четные или нечетные числа независимо от входного семени.Тем не менее, он имеет ограничения - он повторяется относительно быстро, и почти в каждой реализации выдает только числа до 32767.
C не имеет другого встроенного генератора случайных чисел.Если вам нужен действительно сложный пакет, в Интернете доступно множество пакетов, но алгоритм Mersenne Twister , вероятно, является наиболее популярным выбором.
Теперь, если выинтересует причины почему вышеизложенное верно, вот подробные сведения о том, как rand()
работает:
rand()
- это то, что называется " линейный конгруэнтный генератор «.Это означает, что используется уравнение вида:
x n + 1 = (* a **** x n + ***b *) mod m
, где x n - это n th случайное число и a и b - некоторые заранее определенные целые числа.Арифметика выполняется по модулю м , причем м обычно 2 32 в зависимости от станка, так что при вычислении x * сохраняются только самые младшие 32 бита.1061 * n + 1 .
Таким образом, на английском языке идея такова: чтобы получить следующее случайное число, умножьте последнее случайное число на что-то, добавьте к нему число и затем возьмитепоследние несколько цифр.
Несколько ограничений быстро становятся очевидными:
Во-первых, вам нужно начальное случайное число.Это «семя» вашего генератора случайных чисел, и именно здесь вы слышали об использовании time.h
.Так как мы хотим получить действительно случайное число, обычной практикой является спросить у системы, который час (в целочисленной форме), и использовать это в качестве первого «случайного числа».Кроме того, это объясняет, почему при использовании одного и того же начального числа дважды всегда дает точно одинаковую последовательность случайных чисел.Это звучит плохо, но на самом деле полезно, так как отладка намного проще, когда вы управляете входами в вашу программу
Second, a и b нужно выбирать очень, очень тщательно, иначе вы получите некоторые катастрофические результаты.К счастью, уравнение для линейного конгруэнтного генератора достаточно простое, что математика была разработана с некоторыми подробностями.Оказывается, что выбор a , который удовлетворяет * a *** mod8 = 5 вместе с *** b * = 1, гарантирует, что все m целые числа одинаково вероятны, независимо от выбора семян.Вы также хотите получить значение a , которое действительно велико, так что каждый раз, когда вы умножаете его на x n , вы запускаете модуль и отсекаете многоцифр, или же много чисел подряд будут просто кратны друг другу.В результате два общих значения a (например) равны 1566083941 и 1812433253 согласно Кнуту.Библиотека GNU C использует a = 1103515245 и b = 12345.Список значений для многих реализаций доступен на странице в Википедии для LCG .
В-третьих, линейный конгруэнтный генератор фактически повторяется из-за этого по модулю. Это может быть довольно умопомрачительной математикой, но результат всего этого, к счастью, очень прост: последовательность повторяется после генерации чисел m . В большинстве случаев это означает, что ваш генератор случайных чисел будет повторяться каждые 2 32 цикла. Это звучит как много, но это действительно не для многих приложений. Если вы выполняете серьезную численную работу с симуляциями Монте-Карло, это число безнадежно неадекватно.
Четвертая, гораздо менее очевидная проблема заключается в том, что числа на самом деле не действительно случайные. У них забавная связь. Если вы берете три целых числа подряд ( x , y , z ) из LCG с некоторым значением a и m , эти три точки будут всегда падать на решетку точек, генерируемых всеми линейными комбинациями трех точек (1, a, a 2 ), (0, м, 0), (0, 0, м). Это известно как теорема Марсальи, и если вы ее не понимаете, ничего страшного. Все это означает следующее: триплеты случайных чисел из LCG покажут корреляции на некотором глубоком, глубоком уровне. Обычно это слишком глубоко для вас или меня, чтобы заметить, но это там. Можно даже восстановить первое число в «случайной» последовательности из трех чисел, если вам дают второе и третье! Это плохо для криптографии.
Хорошая часть в том, что LCG, такие как rand()
, занимают очень и очень мало места. Обычно для сохранения состояния требуется только 32 бита, что действительно хорошо. Это также очень быстро, требует очень мало операций. Это хорошо для некритических встроенных систем, видеоигр, казуальных приложений и тому подобного.
PRNG - увлекательная тема. Википедия - это всегда хорошее место, если вы хотите узнать больше об истории или различных реализациях, которые существуют сегодня.