Когда в программировании возникает потребность в случайных числах, разработчики обычно прибегают к генераторам псевдослучайных величин. Такие функции на основе исходного «зерна» вычисляют последовательность по заданному алгоритму. Следовательно, истинной случайности в программе нет: зная начальные параметры и алгоритм, можно предсказать все последующие значения. Наша неопределённость объясняется незнанием значений семени и формулы генерации.
В языке C значение семени по умолчанию фиксировано. В примере ниже при каждом запуске будет выводиться одна и та же серия чисел:
#include <stdio.h>
#include <stdlib.h>
int main() {
for (int i = 1; i <= 5; i++) {
printf("%d ", rand());
}
return 0;
}
Результат первого и повторного запусков:
1804289383 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());
}
return 0;
}
Первый и последующие запуски при динамическом семени:
1370043538 851170309 353619136 1517173109 1831772572
1055320706 651477646 2063951383 44502261 1674710625
Разумеется, время можно корректировать, но для большинства приложений (не связанных с криптографией) такой случайности вполне достаточно.
В Python явной установки семени обычно не требуется: интерпретатор при старте извлекает «шум» из внешних источников. В Linux это энтропия, собранная драйверами устройств, — предсказать её сложнее, чем текущее время, поэтому достигается близкое к истинному случайность:
from random import random
for _ in range(5):
value = random()
print(round(value, 2), end=' ')
Пример первого и повторного запусков:
0.89 0.21 0.45 0.71 0.35
0.56 0.27 0.7 0.96 0.05
Нас не интересует, как усилить непредсказуемость. Главное, что и в C, и в Python для появления в программе «неузнаваемой» случайности семя обязано поступать извне. Приложение воспринимает систему-источник как «чёрный ящик» и не знает её внутренней структуры. Чтобы внутри программы возникла истинная случайность, она и «чёрный ящик» должны быть открыты и обмениваться данными.
Когда мы бросаем монету, мы не учитываем силу подброса, угол, поверхность и другие физические параметры — всё это ускользает от нашего восприятия. В результате выпадение орла или решки кажется случайным. Однако, если фиксировать и контролировать эти начальные условия, можно добиться доминирования одной из сторон — по сути, задавать «семя», чтобы получить предсказуемый результат.
В крупных системах подсистемы могут выступать «семенами» друг для друга. Рассмотрим пример на Python с NumPy, где одна функция передаёт данные в следующую:
import numpy as np
def first(seed):
np.random.seed(seed)
a = np.random.randint(58934, size=5)
print('a:', a)
second(np.random.choice(a))
def second(seed):
np.random.seed(seed)
b = np.random.randint(100, 1000, size=10)
print('b:', b)
third(np.random.choice(b))
def third(seed):
np.random.seed(seed)
c = np.random.randint(-100, 100, size=5)
print('c:', c)
if input('Запустить first? ').lower() in ('y', 'yes', 'д', 'да'):
first(abs(np.random.choice(c)))
main_seed = np.random.randint(100)
first(main_seed)
При конкретном значении семени каждая функция переходит в заранее определённое состояние, то есть генерирует один из конечного множества возможных массивов. Чем меньше вариаций семени, тем меньше разнообразие состояний системы.
Тем не менее при каждом запуске нашей программы main_seed определяется из внешних источников, поэтому результаты меняются. Если же в основной ветке явно вызвать seed с фиксированным значением, то при каждом запуске будут генерироваться одни и те же массивы.
Представим полностью закрытую систему без внешних связей. Тогда семя должно быть зашито в момент старта, что заранее предопределит весь ход программы и ограничит число возможных исходов. Истинная случайность исчезает. Однако если внутри такой системы появится механизм, способный анализировать собственное состояние и самостоятельно изменять семя, влияние первоначального значения утратит значение: дальнейшее развитие перестанет зависеть от исходного семени.


