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

# Random\Randomizer::getFloat

(PHP 8 >= 8.3.0)

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

### Описание

public function 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`, — иногда возвращаются некорректные значения.

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

- Random\Randomizer::nextFloat() - Получает число с плавающей точкой из открытого справа интервала [0.0, 1.0)
- Random\Randomizer::getInt() - Получает равномерно выбранное целое число

`↑` and `↓` to navigate • `Enter` to select • `Esc` to close • `/` to open