Как стать автором
Обновить

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

Уровень сложностиПростой
Время на прочтение4 мин
Количество просмотров1.8K

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

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

Сегодня мы заглянем под капот 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 под капотом

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

Теги:
Хабы:
+10
Комментарии4

Публикации

Истории

Работа

PHP программист
72 вакансии

Ближайшие события

4 – 5 апреля
Геймтон «DatsCity»
Онлайн
8 апреля
Конференция TEAMLY WORK MANAGEMENT 2025
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань
20 – 22 июня
Летняя айти-тусовка Summer Merge
Ульяновская область