Привет, Хабр!
Вы когда-нибудь задумывались, как компьютер, эта идеальная детерминированная машина, выполняющая команды с математической точностью, умудряется генерировать «случайные» числа? Ведь в его цифровом мире нет места настоящему хаосу — только чёткие алгоритмы и предсказуемые состояния.
Сегодня мы заглянем под капот PHP и разберёмся, как устроена эта иллюзия случайности. Вы узнаете:
-
Как работает алгоритм Mersenne Twister — сердце генерации случайных чисел в PHP
-
Почему эти числа лишь «псевдослучайные» и как их можно предсказать
-
Чем отличается rand() от mt_rand() и почему оба не подходят для криптографии
-
Как устроен исходный код генератора в 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); }
Что здесь важно:
-
Магическое число 1812433253 было выбрано для лучшего рассеивания битов создателями алгоритма
-
Операция x ^ (x >> 30) помогает «размазать» влияние seed по всему состоянию
-
Добавление + 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/
Добавить комментарий