PHP под капотом: как работает генерация случайных чисел

от автора

Привет, Хабр!

Вы когда-нибудь задумывались, как компьютер, эта идеальная детерминированная машина, выполняющая команды с математической точностью, умудряется генерировать «случайные» числа? Ведь в его цифровом мире нет места настоящему хаосу — только чёткие алгоритмы и предсказуемые состояния.

Сегодня мы заглянем под капот PHP и разберёмся, как устроена эта иллюзия случайности. Вы узнаете:

  1. Как работает алгоритм Mersenne Twister — сердце генерации случайных чисел в PHP

  2. Почему эти числа лишь «псевдослучайные» и как их можно предсказать

  3. Чем отличается rand() от mt_rand() и почему оба не подходят для криптографии

  4. Как устроен исходный код генератора в PHP 8.x

От простого к сложному: эволюция генераторов в PHP

История генераторов случайных чисел в PHP — это путь от примитивных алгоритмов к более совершенным решениям:

// PHP 4/5 (устаревший вариант) $random = rand(0, 100);   // PHP 5+ (улучшенная версия) $betterRandom = mt_rand(0, 100);  // PHP 7+ (криптографически безопасный) $secureRandom = random_int(0, 100);

Но сегодня мы сосредоточимся на mt_rand(), который использует алгоритм Mersenne Twister — один из самых популярных генераторов псевдослучайных чисел.

Mersenne Twister: японская точность в мире случайности

Разработанный в 1997 году Макото Мацумото и Такудзи Нисимура, этот алгоритм получил своё название в честь чисел Мерсенна (простых чисел вида 2^n — 1). Его ключевые особенности:

  • Огромный период повторения: 2¹⁹⁹³⁷ — 1 чисел

  • Высокая скорость работы

  • Хорошее распределение значений

Но как это работает на практике? Как это реализовано в PHP? Давайте разберём по шагам.

1. Инициализация: где берётся случайность

Всё начинается с «зерна» (seed) — начального значения, которое превращает детерминированный алгоритм в генератор «случайных» чисел:

пометка: весь код взят с исходника PHP: исходный код

// Пример инициализации из исходного кода PHP PHPAPI inline void php_random_mt19937_seed32(php_random_status_state_mt19937 *state, uint32_t seed) { uint32_t i, prev_state; state->state[0] = seed; for (i = 1; i < N; i++) { prev_state = state->state[i - 1]; state->state[i] = (1812433253U * (prev_state  ^ (prev_state  >> 30)) + i) & 0xffffffffU; } state->count = i;  mt19937_reload(state); }

Что здесь важно:

  1. Магическое число 1812433253 было выбрано для лучшего рассеивания битов создателями алгоритма

  2. Операция x ^ (x >> 30) помогает «размазать» влияние seed по всему состоянию

  3. Добавление + i гарантирует, что даже при seed=0 состояния будут разными

Шаг 2: Генерация чисел — сердце Mersenne Twister

Когда вы вызываете mt_rand(), происходит следующее:

static php_random_result generate(void *state) { php_random_status_state_mt19937 *s = state; uint32_t s1;        // Если числа закончились - перезагружаем состояние if (s->count >= N) { mt19937_reload(s); }      // Берём следующее число из массива состояний s1 = s->state[s->count++];      // Применяем "tempering transform" для улучшения распределения s1 ^= (s1 >> 11); s1 ^= (s1 << 7) & 0x9d2c5680U; s1 ^= (s1 << 15) & 0xefc60000U;  return (php_random_result){ .size = sizeof(uint32_t), .result = (uint64_t) (s1 ^ (s1 >> 18)), }; }

Ключевые моменты:

  • Каждое число берётся из предварительно вычисленного массива state

  • Темперирование (битовые операции) скрывает линейные зависимости

  • После 624 чисел состояние полностью пересчитывается

Шаг 3: Перезагрузка состояния (reload)

Каждые 624 вызова массив состояний полностью обновляется:

static inline void mt19937_reload(php_random_status_state_mt19937 *state) { uint32_t *p = state->state;  if (state->mode == MT_RAND_MT19937) {         // Основной цикл перемешивания for (uint32_t i = N - M; i--; ++p) { *p = twist(p[M], p[0], p[1]); }         // Обработка хвоста массива for (uint32_t i = M; --i; ++p) { *p = twist(p[M-N], p[0], p[1]); } *p = twist(p[M-N], p[0], state->state[0]); } else { for (uint32_t i = N - M; i--; ++p) { *p = twist_php(p[M], p[0], p[1]); } for (uint32_t i = M; --i; ++p) { *p = twist_php(p[M-N], p[0], p[1]); } *p = twist_php(p[M-N], p[0], state->state[0]); }  state->count = 0; } 

Где twist — это макрос, который перемешивает биты:

#define N             624                 /* length of state vector */ ZEND_STATIC_ASSERT( N == sizeof(((php_random_status_state_mt19937*)0)->state) / sizeof(((php_random_status_state_mt19937*)0)->state[0]), "Assumed length of Mt19937 state vector does not match actual size." ); #define M             (397)                /* a period parameter */ #define hiBit(u)      ((u) & 0x80000000U)  /* mask all but highest   bit of u */ #define loBit(u)      ((u) & 0x00000001U)  /* mask all but lowest    bit of u */ #define loBits(u)     ((u) & 0x7FFFFFFFU)  /* mask     the highest   bit of u */ #define mixBits(u, v) (hiBit(u) | loBits(v)) /* move hi bit of u to hi bit of v */  #define twist(m,u,v)  (m ^ (mixBits(u,v) >> 1) ^ ((uint32_t)(-(int32_t)(loBit(v))) & 0x9908b0dfU)) #define twist_php(m,u,v)  (m ^ (mixBits(u,v) >> 1) ^ ((uint32_t)(-(int32_t)(loBit(u))) & 0x9908b0dfU))

Все числа, что вам кажутся случайными в этом алгоритме, к примеру ограничение в 624 числа в state, на самом деле были подобраны почти в ручную создателями алгоритма для большей степени рандомизации!

Так мы узнали, как создаются «случайные» числа в PHP с помощью алгоритма Mersenne Twister и разобрали исходный код на языке C. Но почему эти числа называются псевдослучайными? Разве их можно угадать? Да, можно. Зная 624 последовательных числа, можно восстановить состояние, поэтому даже в документации PHP есть следующая пометка:

Как создаются безопасные даже для криптографии случайные значения мы разберемся в следующей статье.

Спасибо, что были с нами в этом глубоком погружении в генерацию случайных чисел в PHP! Надеюсь, вам было так же интересно разбирать исходный код, как и мне. Но это только верхушка айсберга — в PHP скрыто ещё множество удивительных механизмов, о которых мы могли бы поговорить в будущем. К примеру:

  • Как PHP общается с внешним миром: работа с сетями и HTTP

  • Магия интроспекции: Reflection API под микроскопом

  • JIT-компиляция в PHP 8 — революция под капотом

  • Асинхронный PHP: от корутин до Fibers под капотом

Всем спасибо за внимание!


ссылка на оригинал статьи https://habr.com/ru/articles/896650/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *