Statement on glibc/iconv Vulnerability

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() реализует алгоритм, который будет возвращать равномерно выбранное число с плавающей точкой из максимально возможного набора точно представимых и равнораспределённых чисел с плавающей точкой в пределах запрошенного интервала. Расстояние между выбираемыми числами с плавающей точкой («размер шага») соответствует расстоянию между числами с плавающей точкой с наименьшей плотностью, т. е. расстоянию между числами с плавающей точкой на границе интервала с большим абсолютным значением. То есть не всем представимым числам с плавающей точкой в пределах этого интервала разрешено возвращаться, если интервал пересекает одну или несколько степеней двойки. Шаг начнется с границы интервала с большим абсолютным значением, чтобы гарантировать, что шаги совпадают с точно представимыми числами с плавающей точкой.

Границы закрытых интервалов всегда будут включены в набор выбираемых плавающих значений. Так, если размер интервала не кратен размеру шага и граница с меньшим абсолютным значением — это замкнутая граница, расстояние между этой границей и ближайшим к ней выбираемым числом с плавающей точкой будет меньше размера шага.

Предостережение

Постобработка возвращённых чисел с плавающей точкой, скорее всего, нарушит равномерное равнораспределение, поскольку промежуточные плавающие значения в математической операции неявно округляются. Запрошенный интервал должен как можно точнее соответствовать нужному интервалу, а округление должно выполняться только как явная операция непосредственно перед отображением выбранного числа пользователю.

Объяснение алгоритма с примерами значений

Чтобы привести пример работы алгоритма, рассмотрим представление с плавающей точкой, в котором выбрана 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.
  • Если запрошенный интервал не содержит значений, будет выброшено исключение 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 , чтобы получить нужные поведенческие свойства.

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

add a note

User Contributed Notes

There are no user contributed notes for this page.
To Top