(PHP 8 >= 8.3.0)
Random\Randomizer::getFloat — Получает равномерно выбранное число с плавающей точкой
$min, float $max, Random\IntervalBoundary $boundary = Random\IntervalBoundary::ClosedOpen): floatМетод возвращает равномерно выбранное равнораспределённое число с плавающей точкой из заданного интервала.
Из-за фундаментальных ограничений двоичной архитектуры
не каждое вещественное число возможно точно представить в памяти в виде числа с плавающей точкой.
Числа, которые невозможно представить точно, округляются до ближайшего
точного представления.
На числовой оси плотность чисел с плавающей точкой неравномерна.
В двоичном представлении чисел с плавающей точкой экспонента удваивает длину каждого следующего интервала:
[1.0, 2.0), [2.0, 4.0), [4.0, 8.0), [8.0, 16.0),….
При этом из-за фиксированного количества битов для хранения мантиссы
количество значений внутри отдельного интервала остаётся одинаковым,
поэтому расстояние между соседними значениями в очередном интервале удваивается.
Из-за неравномерной плотности чисел с плавающей точкой на числовой оси выбор случайного числа в пределах произвольно заданного интервала, — например, путём деления двух целых чисел, — смещает распределение. Из-за вынужденного округления одни числа с плавающей точкой возвращаются чаще остальных, а вблизи границ интервалов, по обе стороны от которых плотность чисел с плавающей точкой меняется скачкообразно, эффект усиливается.
Метод Random\Randomizer::getFloat() работает на базе алгоритма, который равномерно выбирает значение из наибольшего возможного в пределах заданного интервала подмножества точно представимых равнораспределённых чисел с плавающей точкой. «Шагом» для выбора случайного числа с плавающей точкой становится расстояние между значениями двоичного интервала с наименьшей плотностью, которому принадлежит граница с бо́льшим абсолютным значением. Поэтому из заданного аргументами интервала вернётся не каждое представимое число с плавающей точкой, если интервал пересечёт одну или больше степеней двойки. Проход равноразмерными шагами начнётся с правой границы самого разреженного интервала, чтобы гарантировать совпадение шагов c точно представимыми числами с плавающей точкой.
Закрытые границы интервалов принудительно включаются в набор значений с плавающей точкой для случайного выбора. Расстояние между границей с меньшим абсолютным значением и ближайшим числом с плавающей точкой окажется меньше размера шага, если размер интервала не кратен размеру шага и граница с меньшим абсолютным значением закрыта.
Постобработка чисел с плавающей точкой, которые возвращает метод, наверняка нарушит равномерное равнораспределение, поскольку промежуточные плавающие значения внутри математических операций неявно округляются, поэтому интервал, в пределах которого требуется получить случайные значения, сразу предельно точно задают аргументами, а округляют случайные значения только сознательно — непосредственно перед выводом случайного числа пользователю.
Работу алгоритма проиллюстрирует пример,
в котором для представления мантиссы числа с плавающей точкой отводится только 3 бита.
Между соседними степенями двойки в трёхбитном поле мантиссы
возможно представить только 8 значений с плавающей точкой.
При этом между степенями двойки 1.0 и 2.0 без потери точности
получится представить только значения с шагом 0.125, а между степенями 2.0 и 4.0 —
только представления значений с шагом 0.25.
Для представления мантиссы числа с плавающей точкой в PHP предусмотрели 52-битное поле,
в котором между соседними степенями двойки возможно представить
252 значений.
Между степенями 1.0 и 4.0
возможно точно представить следующие числа с плавающей точкой:
1.01.1251.251.3751.51.6251.751.8752.02.252.52.753.03.253.53.754.0
Теперь выполним вызов $randomizer->getFloat(1.625, 2.5, IntervalBoundary::ClosedOpen),
чтобы получить случайное число с плавающей точкой в интервале от 1.625
до 2.5, не включая правую границу.
Алгоритм сначала определяет размер шага на границе с бо́льшим абсолютным значением 2.5.
Размер шага на этой границе равен 0.25.
Обратите внимание: размер запрошенного интервала составляет 0.875,
что нельзя назвать точным кратным шагу 0.25.
При начале движения от нижней границы — 1.625 —
алгоритм столкнулся бы со значением 2.125,
которое из-за ограничений представления чисел в памяти неявно округляется и теряет точность.
Поэтому алгоритм начинает работу с верхней границы — 2.5.
В заданном интервале для случайного выбора доступны следующие числа:
2.252.01.751.6252.5 не включается, поскольку верхняя граница запрошенного
интервала — открыта.
Значение закрытой границы 1.625 включается,
хотя расстояние до ближайшего значения 1.75 составляет 0.125,
что меньше шага в 0.25, размер которого определили прежде.
Значение 1.625 — нижняя граница закрытого слева интервала,
а закрытые границы включаются в выборку.
В заключение из четырёх доступных значений алгоритм равномерно выбирает и возвращает одно случайное значение.
В предыдущем примере внутри каждого подынтервала с границами по степеням двойки
содержится восемь представимых чисел с плавающей точкой.
Следующий пример объяснит, почему деление двух целых чисел не работает для генерации
случайного числа с плавающей точкой. Пусть в открытом справа интервале
от 0.0 до 1.0
лежат 16 равнораспределённых чисел с плавающей точкой.
Одна половина чисел — восемь точно представимых значений в диапазоне
от 0.5 до 1.0, другая —
ещё восемь значении в диапазоне от 0.0 до 1.0.
Размер шага в интервале длиной в единицу с шестнадцатью значениями равен 0.0625.
Набор из шестнадцати значений генерируется путём деления случайного целочисленного значения
в диапазоне от 0 до 15 на число 16:
0.00.06250.1250.18750.250.31250.3750.43750.50.56250.6250.68750.750.81250.8750.9375
Каждое случайное число с плавающей точкой из набора масштабируется до интервала от 1.625
до 2.75 с открытой справа границей путём умножения числа на размер интервала
0.875 и добавления минимума 1.625.
Результатом такого аффинного преобразования становятся следующие значения:
1.625 округляется до 1.6251.679 округляется до 1.6251.734 округляется до 1.751.789 округляется до 1.751.843 округляется до 1.8751.898 округляется до 1.8751.953 округляется до 2.02.007 округляется до 2.02.062 округляется до 2.02.117 округляется до 2.02.171 округляется до 2.252.226 округляется до 2.252.281 округляется до 2.252.335 округляется до 2.252.390 округляется до 2.52.445 округляется до 2.52.5 — включилась в результат,
хотя интервал открыт справа, а открытые границы интервалов исключаются.
Также обратите внимание: вероятность возврата значений 2.0
и 2.25 в два раза выше возврата других значений.
minНижняя граница интервала.
maxВерхняя граница интервала.
boundaryПараметр указывает, требуется ли включать границы интервала в набор возможных значений возврата.
Метод возвращает равномерно выбранное равнораспределённое число с плавающей точкой из интервала,
заданного параметрами min,
max и boundary.
Параметр boundary определяет, войдут ли значения min и max
в набор доступных для возврата случайных значений.
min неконечного числа,
как это определяет функция is_finite(),
метод выбросит ошибку ValueError.
max неконечного числа,
как это определяет функция is_finite(),
метод выбросит ошибку ValueError.
Random\Randomizer::$engine.
Пример #1 Пример генерации случайного числа с плавающей точкой методом Random\Randomizer::getFloat()
<?php
$randomizer = new \Random\Randomizer();
// Обратите внимание, что степень детализации по широте в два раза выше
// степени детализации по долготе.
//
// Для широты допустимы оба значения: -90 и 90.
// Для долготы допустимо значение 180, но не -180,
// поскольку значения -180 и 180 относятся к одной и той же долготе.
printf(
"Широта: %+.6f Долгота: %+.6f",
$randomizer->getFloat(-90, 90, \Random\IntervalBoundary::ClosedClosed),
$randomizer->getFloat(-180, 180, \Random\IntervalBoundary::OpenClosed),
);Вывод приведённого примера будет похож на:
Широта: +69.244304 Долгота: -53.548951
Замечание:
Метод генерирует числа с плавающей точкой на основе алгоритма γ-секции, который описывается в статье » Drawing Random Floating-Point Numbers from an Interval. Frédéric Goualard, ACM Trans. Model. Comput. Simul., 32:3, 2022; за счёт поведенческих характеристик алгоритма метод генерирует статистически достоверные случайные значения.
В алгоритме γ-секции сознательно не обрабатываются близкие к нулю результаты вычислений, которые выходят за пределы нормализованных значений,
поэтому для интервалов с границами в субнормальном диапазоне чисел с плавающей точкой —
границ с абсолютным значением меньше приблизительно
2-1020, или около 8.9e-308, —
иногда возвращаются некорректные значения.