Когда в языках программирования речь заходит о случайных числах, то подчеркивается, что производящие их "генераторы" (специальные функции ил�� объекты) выдают на самом деле псевдослучайные значения. Они, исходя из начального так называемого "зерна", вычисляются по определенной формуле. Таким образом, в самой программе настоящей случайности нет, потому что если известны инициирующие данные и алгоритм, все остальные числа предопределены. Нам значения кажутся случайными лишь потому, что мы не ведаем ни зерен, ни формул.
В Си стартовое зерно задано. В примере ниже каждый раз при выполнении программы будет выводиться один и тот же ряд чисел.
#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 в основной ветке, то при каждом выполнении программы будем получать одни и те же массивы. (Здесь не имеются в виду повторные вызовы функций в процессе одного выполнения. Они будут выдавать разные массивы, так в них будет передаваться отличное от предыдущего зерно.)
Представим, что на каком-то уровне объединения подсистем в единую систему было включено все, что только можно. Ничего за ее пределами больше не существует. В таком случае ей больше не с чем обмениваться данными. Зерно больше нельзя получить извне. Это значит, оно должно быть задано в момент запуска этой программы и предопределит весь ход ее выполнения. Кроме того, не будем забывать, что вариаций зерен может оказаться ограниченное количество, поэтому и способов развития системы не бесконечно.
Получается, что если рано или поздно мы упираемся в закрытую систему, истинной случайности неоткуда взяться (она существует лишь на уровне подсистем, которые обмениваться данными могут, но о принципах работы друг друга не знают). Однако в одной из функций может появиться нечто, способное анализировать другие и себя, менять зерно как считает нужным. В этом случае данные, которыми система была инициирована при запуске, могут иметь значение только до момента появления сознания. После этого дальнейший ход развития системы перестает зависеть от начального зерна.
