Когда в языках программирования речь заходит о случайных числах, то подчеркивается, что производящие их "генераторы" (специальные функции или объекты) выдают на самом деле псевдослучайные значения. Они, исходя из начального так называемого "зерна", вычисляются по определенной формуле. Таким образом, в самой программе настоящей случайности нет, потому что если известны инициирующие данные и алгоритм, все остальные числа предопределены. Нам значения кажутся случайными лишь потому, что мы не ведаем ни зерен, ни формул.
В Си стартовое зерно задано. В примере ниже каждый раз при выполнении программы будет выводиться один и тот же ряд чисел.
#include <stdio.h>
#include <stdlib.h>
int main() {
for (int i = 1; i <= 5; i++) { // пять раз
printf("%d ", rand()); // rand() генерирует число
}
}Первый и n-ный запуски программы на выполнение:
1804289383 846930886 1681692777 1714636915 19577477931804289383 846930886 1681692777 1714636915 1957747793Зерно можно переопределить, передав свое число в функции srand, и ряд будет другим, но все-равно при каждом выполнении программы одинаковым. Поэтому в качестве инициирующего значения часто используют системное время, которое будет взято в момент запуска программы. Разные моменты исполнения — разное системное время.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand(time(NULL)); // установка зерна
for (int i = 1; i <= 5; i++)
printf("%d ", rand());
}Первый и n-ный запуски:
1370043538 851170309 353619136 1517173109 18317725721055320706 651477646 2063951383 44502261 1674710625Конечно, время можно подкрутить, но для пользовательских приложений (не связанных с криптографией и безопасностью) такой случайности обычно достаточно.
В Python нужды менять зерно нет (но если надо задать явно, то соответствующая функция есть как в модуле random, так и библиотеке NumPy), потому что сам интерпретатор берет его из внешних источников при выполнении программы. Так в Linux это "шум" (энтропия), полученный из драйверов устройств. Предсказать и повторить его сложнее, чем системное время. Поэтому можно говорить о почти истинной случайности.
from random import random
for i in range(5): # пять раз
value = random() # получаем случайное число
print(round(value, 2), end=' ') # округляем и выводимПервый и n-ный запуски:
0.89 0.21 0.45 0.71 0.350.56 0.27 0.7 0.96 0.05Сейчас нас не интересует вопрос, как сделать числа более случайными. Обратим внимание, как в Си явно, так и Python неявно, чтобы в программе завелась непредсказуемая и неповторяемая для нее случайность, зерно должно быть получено извне. Программа о том, что происходит во внешней для нее среде, ничего не знает. Система, из которой она получает данные, выступает для нее черным ящиком. Чтобы в программе появилась истинная для нее случайность, и она, и черный ящик должны быть открытыми и обмениваться данными.
Когда мы подкидываем монету, сторона ее падения предопределена в момент броска (флуктуации на тему "вдруг подул ветер" мы игнорируем). Это только для нас орел или решка — случайность. Мы как ограниченная в своих возможностях "программа" не знаем, как влияет сила подкидывания, угол, исходное положение и другое на конечный результат, не измеряем их. Вся эта физика для нас играет роль черного ящика.
Однако если заметить, при каких положении монеты в руке, силе броска, высоте, напольном покрытии она падает орлом, можно натренироваться и свести к минимуму случаи выпадения решки. Это вроде как начать задавать зерно явно для системы с целью получения из нее определенного результата.
Обычно более крупные системы включают в себя другие в качестве составных компонентов. Например, организм — это взаимодействующие между собой системы органов (дыхательная, нервная и др.); каждая в свою очередь состоит из отдельных органов. Представим, что в модельной системе-программе ее подсистемы-функции обмениваются данными, выполняющими роль зерен для получателей.
import numpy as np
def first(s):
# Используем внешние данные как зерно для этой системы:
np.random.seed(s)
# На самом деле зерно меняется для всей программы,
# но приходится этим пренебречь.
# Получаем состояние системы, оно предопределено зерном:
a = np.random.randint(58934, size=5)
print('a:', a)
# Система может передать любой элемент своих данных в другую:
second(np.random.choice(a))
def second(s):
np.random.seed(s)
b = np.random.randint(100, 1000, size=10)
print('b:', b)
third(np.random.choice(b))
def third(s):
np.random.seed(s)
c = np.random.randint(-100, 100, size=5)
print('c:', c)
q = input('Вызвать First? ')
if q in ('y', 'Y', 'Yes', 'yes'):
first(abs(np.random.choice(c)))
main_seed = np.random.randint(100)
first(main_seed)a: [43707 6482 55488 34149 50499]
b: [100 379 621 703 310 668 759 846 128 872]
c: [ 24 -7 -99 -28 44]
Вызвать First? y
a: [42241 4089 26117 55284 7200]
b: [151 315 870 274 409 801 620 830 191 645]
c: [ 7 -48 -97 63 80]
Вызвать First? nКогда, к примеру, система second получает конкретное зерно, она приходит в соответствующее ему состояние, то есть генерируется массив определенных превдослучайных чисел. Перечень возможных таких массивов–состояний соответствует количеству вариантов значений зерна. И если это количество не бесконечно, то конечно и число вариаций состояния системы.
При всем этом при каждом запуске нашей программы будут генерироваться разные числа. Причина кроется в том, что сама программа все еще остается открытой системой. Когда main_seed присваивается случайное число, до этого в самой программе зерно еще нигде не задавалось. Следовательно, раз мы имеем дело с Python, оно было взято из внешних источников. В результате при каждом запуске main_seed имеет разное из сотни значение. Но если мы "закроем" систему вызовом функции seed в основной ветке, то при каждом выполнении программы будем получать одни и те же массивы. (Здесь не имеются в виду повторные вызовы функций в процессе одного выполнения. Они будут выдавать разные массивы, так в них будет передаваться отличное от предыдущего зерно.)
Представим, что на каком-то уровне объединения подсистем в единую систему было включено все, что только можно. Ничего за ее пределами больше не существует. В таком случае ей больше не с чем обмениваться данными. Зерно больше нельзя получить извне. Это значит, оно должно быть задано в момент запуска этой программы и предопределит весь ход ее выполнения. Кроме того, не будем забывать, что вариаций зерен может оказаться ограниченное количество, поэтому и способов развития системы не бесконечно.
Получается, что если рано или поздно мы упираемся в закрытую систему, истинной случайности неоткуда взяться (она существует лишь на уровне подсистем, которые обмениваться данными могут, но о принципах работы друг друга не знают). Од��ако в одной из функций может появиться нечто, способное анализировать другие и себя, менять зерно как считает нужным. В этом случае данные, которыми система была инициирована при запуске, могут иметь значение только до момента появления сознания. После этого дальнейший ход развития системы перестает зависеть от начального зерна.