(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() реализует алгоритм, который будет возвращать равномерно выбранное число с плавающей точкой из максимально возможного набора точно представимых и равнораспределённых чисел с плавающей точкой в пределах запрошенного интервала. Расстояние между выбираемыми числами с плавающей точкой («размер шага») соответствует расстоянию между числами с плавающей точкой с наименьшей плотностью, т. е. расстоянию между числами с плавающей точкой на границе интервала с большим абсолютным значением. То есть не всем представимым числам с плавающей точкой в пределах этого интервала разрешено возвращаться, если интервал пересекает одну или несколько степеней двойки. Шаг начнется с границы интервала с большим абсолютным значением, чтобы гарантировать, что шаги совпадают с точно представимыми числами с плавающей точкой.
Границы закрытых интервалов всегда будут включены в набор выбираемых плавающих значений. Так, если размер интервала не кратен размеру шага и граница с меньшим абсолютным значением — это замкнутая граница, расстояние между этой границей и ближайшим к ней выбираемым числом с плавающей точкой будет меньше размера шага.
Постобработка возвращённых чисел с плавающей точкой, скорее всего, нарушит равномерное равнораспределение, поскольку промежуточные плавающие значения в математической операции неявно округляются. Запрошенный интервал должен как можно точнее соответствовать нужному интервалу, а округление должно выполняться только как явная операция непосредственно перед отображением выбранного числа пользователю.
Чтобы привести пример работы алгоритма, рассмотрим представление с плавающей точкой,
в котором выбрана 3-битная мантисса.
Это представление способно представлять 8 различных значений с плавающей точкой
между последовательными степенями двух.
То есть между
1.0
и 2.0
все шаги размером 0.125
точно представимы и между 2.0
и 4.0
все шаги размером 0.25
точно представимы.
В PHP для работы с числами с плавающей точкой выбрана 52-битная мантисса
и PHP может представлять 252
разных значении между каждой степенью двойки.
Это означает, что
1.0
1.125
1.25
1.375
1.5
1.625
1.75
1.875
2.0
2.25
2.5
2.75
3.0
3.25
3.5
3.75
4.0
1.0
и 4.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.25
2.0
1.75
1.625
2.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.0
0.0625
0.125
0.1875
0.25
0.3125
0.375
0.4375
0.5
0.5625
0.625
0.6875
0.75
0.8125
0.875
0.9375
Это случайное число с плавающей точкой можно масштабировать до интервала с открытым интервалом справа от 1.625
до 2.75
(не включая последнее), умножив его на размер интервала
(0.875
) и добавив минимум 1.625
.
Это так называемое аффинное преобразование приведёт к значениям:
1.625
округляется до 1.625
1.679
округляется до 1.625
1.734
округляется до 1.75
1.789
округляется до 1.75
1.843
округляется до 1.875
1.898
округляется до 1.875
1.953
округляется до 2.0
2.007
округляется до 2.0
2.062
округляется до 2.0
2.117
округляется до 2.0
2.171
округляется до 2.25
2.226
округляется до 2.25
2.281
округляется до 2.25
2.335
округляется до 2.25
2.390
округляется до 2.5
2.445
округляется до 2.5
2.5
,
несмотря на то, что это открытая граница и, следовательно, исключена.
Также обратите внимание, что вероятность возвращата значений 2.0
и 2.25
в два раза выше, чем у других значений.
min
Нижняя граница интервала.
max
Верхняя граница интервала.
boundary
Указывает, являются ли границы интервала возможными возвращаемыми значениями.
Возвращает равномерно выбранное равнораспределённое число с плавающей точкой из интервала,
заданного параметрами min
,
max
и boundary
.
Возможные возвращаемые значения min
и max
зависят
от значения параметра boundary
.
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 , чтобы получить нужные поведенческие свойства.