§3. Математические датчики: станок, который сам плодит цифру
Железо отпало, тетрадь со случайными цифрами жрёт память и таскать её западло. Нормальному пацану нужен станок прямо в процессоре: коротенький алгоритм, который сам, из головы, штампует псевдослучайные числа — быстро, пачками, и чтоб при нужде можно было отмотать тот же расклад назад (задал стартовое число — получил ту же серию). Вот такие станки и называют математическими датчиками.
Устроены они обычно как рекуррентные алгоритмы: следующее число лепится из предыдущего. Задал старт — и понеслась цепочка , где каждое вытекает из соседа слева. Коротко, детерминированно, воспроизводимо. Разберём от простого к рабочему.
Метод середины квадрата: красиво, но гнилой
Первый заход — метод середины квадрата (Джон фон Нейман, 1946). Схема детская:
- Берём четырёхзначное число .
- Возводим в квадрат (получаем до восьми цифр).
- Выдираем средние четыре цифры — это .
- Нормируем: (то есть — загоняем в ).
- С повторяем всё заново, и так далее.
Пример от Лагутина. Старт . Квадрат: . Средние четыре цифры — , значит и . Дальше , середина — , выходит . И покатилось.
Погоняй сам — жми Run:
Красиво? Красиво. Только датчик гнилой, и вот где он палится:
- Старт решает всё. полностью задаёт всю цепочку — никакой свободы, вся «случайность» зашита в одно число.
- Зацикливается быстро — не позже чем через шагов (четырёхзначных чисел всего-то десять тысяч). Хуже: есть числа, которые воспроизводят сами себя и намертво застревают. Например , середина — снова . Приехали, датчик встал на месте.
- Можно убить на ровном месте. Задашь неудачный старт (скажем, или ) — и датчик через пару шагов схлопывается в ноль и дальше плюёт одни нули. Полное фуфло.
Глянь на этот кидок своими глазами:
Вывод: красивая схема, но для серьёзного дела не катит. Нужен станок покрепче.
Линейный конгруэнтный датчик: рабочая лошадь
Вот это уже по-взрослому. Общая схема мультипликативного (и чуть шире — линейного конгруэнтного) датчика. Задаём стартовое , множитель , приращение и делитель , и крутим по формуле:
Разбираем по кирпичикам, тут один новый значок:
- — это остаток от деления на . Поделил нацело, что осталось в хвосте — то и берём. Фишка в том, что остаток всегда сидит в диапазоне — то есть сколько ни умножай, результат всегда загнан в рамки. Как счётчик по кругу: добежал до — и обнулился.
- — берём прошлое число, раскручиваем умножением на , добавляем .
- — остаток этой движухи по модулю .
- — делим на , чтобы загнать в .
При датчик называют мультипликативным — приращения нет, только умножение по кругу.
Теперь главное — какие и брать, чтобы датчик держал марку. Делитель выгодно брать простым: например (ровно влезает в 32-битное целое). А множитель подбирают так, чтобы цепочка пробежала все возможные значения от до , прежде чем зациклиться — тогда период максимальный. Фишман и Мур после изучения кучи вариантов предложили, в частности, .
Покрути датчик руками. Ниже — решётка пар : берём соседние числа и кидаем точкой на плоскость. На пресете «Кривой» сразу видно палево — точки садятся на несколько прямых (датчик с грубым модулем предсказуем). На «Хорошем (MINSTD)» — ровная заливка, закономерности не видно:
RANDU: самый знаменитый кидок в истории датчиков
А теперь поучительная история про то, как целое поколение инженеров развели. В библиотеке SSP для машин IBM-360 жил датчик RANDU: , множитель , . На пары он смотрится прилично — в плоскости палева нет. Им пользовались годами и не чуяли подвоха.
А подвох был жирнейший. Если брать числа тройками и кидать их точками в трёхмерный куб, они не заполняют его, как положено честным независимым числам, а ложатся всего на 15 плоскостей:
То есть «случайные» тройки на самом деле сидят по струнке на полутора десятках плоскостей — в 2D этого не видать, а в 3D вылезает наружу. Классический случай: датчик выглядит чётко, пока не посмотришь под правильным углом.
Покрути куб. Переключи на RANDU и поймай ракурс вдоль диагонали — точки схлопнутся в плоскости. А на «хорошем» датчике облако заполняет куб ровно, без строя:
Мораль простая и злая: датчик может выглядеть чистым на одном тесте и с треском спалиться на другом. Нельзя верить генератору на слово — его надо гонять по проверкам.
Уичман–Хилл: три датчика в одной упряжке
Как сделать надёжно? Не полагаться на один станок, а запустить несколько сразу и смешать. Датчик Уичмана–Хилла (1982) гоняет три мультипликативных датчика параллельно, с такими параметрами:
На -м шаге каждый выдаёт своё , а итог — дробная часть их суммы: (фигурные скобки — «взять только то, что после запятой»).
Выхлоп: период такого комбо — около . Это на порядки длиннее периода одиночного датчика Фишмана–Мура (), да ещё и на компьютере считается в несколько раз быстрее. Трое по понятиям держат район куда дольше, чем один.
Что в сухом остатке
Математический датчик — это короткий рекуррентный алгоритм: задал старт (seed) — получил воспроизводимую цепочку псевдослучайных чисел, быстро и без тетрадей. Но:
- старт полностью определяет серию (это и плюс — воспроизводимость, и зона риска — кривой старт губит всё);
- у любого датчика есть период, после которого он идёт на повтор;
- и, как показал RANDU, датчик может таить закономерность, незаметную на простых тестах.
Поэтому генератору нельзя верить на глаз. А чтобы вообще понять, что значит «настоящая случайность» и почему короткий алгоритм в принципе не может её выдать, — переходим к следующему параграфу, про сложность.