Random\Randomizer::getFloat

(PHP 8 >= 8.3.0)

Random\Randomizer::getFloatПолучает равномерно выбранное число с плавающей точкой

Описание

public Random\Randomizer::getFloat(float $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.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

Теперь выполним вызов $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. Параметр boundary определяет, войдут ли значения min и max в набор доступных для возврата случайных значений.

Ошибки

  • При передаче в аргументе min неконечного числа, как это определяет функция is_finite(), метод выбросит ошибку ValueError.
  • При передаче в параметр max неконечного числа, как это определяет функция is_finite(), метод выбросит ошибку ValueError.
  • Метод выбросит ошибку ValueError, если запрошенный интервал не содержит значений.
  • Любые Throwable, выбрасываемые методом Random\Engine::generate() базового 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, — иногда возвращаются некорректные значения.

Смотрите также

Добавить

Примечания пользователей

Пользователи ещё не добавляли примечания для страницы
To Top