Открытость систем и «чёрный ящик» как источник случайности

Когда в программировании возникает потребность в случайных числах, разработчики обычно прибегают к генераторам псевдослучайных величин. Такие функции на основе исходного «зерна» вычисляют последовательность по заданному алгоритму. Следовательно, истинной случайности в программе нет: зная начальные параметры и алгоритм, можно предсказать все последующие значения. Наша неопределённость объясняется незнанием значений семени и формулы генерации.

В языке 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 с фиксированным значением, то при каждом запуске будут генерироваться одни и те же массивы.

Представим полностью закрытую систему без внешних связей. Тогда семя должно быть зашито в момент старта, что заранее предопределит весь ход программы и ограничит число возможных исходов. Истинная случайность исчезает. Однако если внутри такой системы появится механизм, способный анализировать собственное состояние и самостоятельно изменять семя, влияние первоначального значения утратит значение: дальнейшее развитие перестанет зависеть от исходного семени.

 

Источник

Читайте также