PHP Conference Japan 2024

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

从请求的区间中返回一个均匀选取的、等分布的浮点数。

由于精度有限,并非所有实数都可以精确地表示为浮点数。如果一个数不能精确表示,则将其四舍五入到最接近的可表示的精确值。此外,浮点数在整个数轴上并不等密。由于浮点数使用二进制指数,因此两个相邻浮点数之间的距离在每个 2 的幂次方处翻倍。换句话说:从 1.02.0 之间存在与从 2.04.04.08.08.016.0 等区间之间相同数量的可表示浮点数。

出于这个原因,例如通过将两个整数相除来随机采样请求区间内的任意数字,可能会导致偏差分布。必要的舍入会导致某些浮点数比其他浮点数返回的次数更多,尤其是在浮点数密度发生变化的 2 的幂次方附近。

Random\Randomizer::getFloat() 实现了一种算法,该算法将从请求区间内最大可能的、精确可表示且等分布的浮点数集中均匀选取一个浮点数。可选浮点数之间的距离(“步长”)与密度最低的浮点数之间的距离相匹配,即绝对值较大的区间边界处的浮点数之间的距离。这意味着如果区间跨越了一个或多个 2 的幂次方,则给定区间内并非所有可表示的浮点数都可能被返回。步进将从绝对值较大的区间边界开始,以确保步长与精确可表示的浮点数对齐。

闭区间边界将始终包含在可选浮点数集中。因此,如果区间的尺寸不是步长的精确倍数,并且绝对值较小的边界是闭边界,则该边界与其最接近的可选择浮点数之间的距离将小于步长。

注意

对返回的浮点数进行后处理可能会破坏均匀等分布,因为数学运算中的中间浮点数会经历隐式舍入。请求的区间应尽可能地匹配所需的区间,并且舍入应仅作为显式操作在将选定的数字显示给用户之前执行。

使用示例值的算法说明

为了举例说明算法的工作原理,请考虑一个使用 3 位尾数的浮点表示。这种表示能够表示连续的 2 的幂次方之间的 8 个不同的浮点值。这意味着在 1.02.0 之间,所有大小为 0.125 的步长都是精确可表示的,而在 2.04.0 之间,所有大小为 0.25 的步长都是精确可表示的。实际上,PHP 的浮点数使用 52 位尾数,并且可以在每个 2 的幂次方之间表示 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.04.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)处是闭合的,并且闭合边界始终包含在内。

最后,算法随机地从四个可选值中均匀选取一个并返回它。

为什么将两个整数相除不起作用

在前面的示例中,在由 2 的幂次方分隔的每个子区间之间有 8 个可表示的浮点数。为了举例说明为什么将两个整数相除不能很好地生成随机浮点数,请考虑在从 0.0 开始直到但不包括 1.0 的右开区间中有 16 个等分布的浮点数。其中一半是 0.51.0 之间 8 个精确可表示的值,另一半是在 0.01.0 之间步长为 0.0625 的值。这些可以通过将 015 之间的随机整数除以 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

此随机浮点数可以通过将其乘以区间的大小(0.875)并加上最小值 1.625 来缩放为从 1.625 开始直到但不包括 2.75 的右开区间。这种所谓的仿射变换将导致以下值:

  • 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.02.25 返回的可能性是其他值的的两倍。

参数

min

区间的下界。

max

区间的上界。

boundary

指定区间边界是否为可能的返回值。

返回值

minmaxboundary 指定的区间中均匀选取的、等分布的浮点数。minmax 是否为可能的返回值取决于 boundary 的值。

错误/异常

示例

示例 #1 Random\Randomizer::getFloat() 示例

<?php
$randomizer
= new \Random\Randomizer();

// 请注意,纬度的粒度是经度粒度的两倍。
//
// 纬度的值可以是 -90 和 90。
// 经度的值可以是 180,但不能是 -180,因为
// -180 和 180 指的是同一个经度。
printf(
"Lat: %+.6f Lng: %+.6f",
$randomizer->getFloat(-90, 90, \Random\IntervalBoundary::ClosedClosed),
$randomizer->getFloat(-180, 180, \Random\IntervalBoundary::OpenClosed),
);
?>

以上示例将输出类似以下内容

Lat: +69.244304 Lng: -53.548951

备注

注意:

此方法实现了发表在 »  从区间中绘制随机浮点数。Frédéric Goualard,ACM Trans. Model. Comput. Simul.,32:3,2022 中的 γ-截面算法,以获得所需的行为属性。

另请参阅

添加注释

用户贡献的注释

此页面没有用户贡献的注释。
To Top