Привет, Хабр!
Вы когда-нибудь задумывались, как компьютер, эта идеальная детерминированная машина, выполняющая команды с математической точностью, умудряется генерировать "случайные" числа? Ведь в его цифровом мире нет места настоящему хаосу - только чёткие алгоритмы и предсказуемые состояния.
Сегодня мы заглянем под капот 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 под капотом
Всем спасибо за внимание!