(PHP 8 >= 8.3.0)
Random\Randomizer::getFloat — 获取均匀选取的浮点数
$min
, float $max
, Random\IntervalBoundary $boundary
= Random\IntervalBoundary::ClosedOpen): float从请求的区间中返回一个均匀选取的、等分布的浮点数。
由于精度有限,并非所有实数都可以精确地表示为浮点数。如果一个数不能精确表示,则将其四舍五入到最接近的可表示的精确值。此外,浮点数在整个数轴上并不等密。由于浮点数使用二进制指数,因此两个相邻浮点数之间的距离在每个 2 的幂次方处翻倍。换句话说:从 1.0
到 2.0
之间存在与从 2.0
到 4.0
、4.0
到 8.0
、8.0
到 16.0
等区间之间相同数量的可表示浮点数。
出于这个原因,例如通过将两个整数相除来随机采样请求区间内的任意数字,可能会导致偏差分布。必要的舍入会导致某些浮点数比其他浮点数返回的次数更多,尤其是在浮点数密度发生变化的 2 的幂次方附近。
Random\Randomizer::getFloat() 实现了一种算法,该算法将从请求区间内最大可能的、精确可表示且等分布的浮点数集中均匀选取一个浮点数。可选浮点数之间的距离(“步长”)与密度最低的浮点数之间的距离相匹配,即绝对值较大的区间边界处的浮点数之间的距离。这意味着如果区间跨越了一个或多个 2 的幂次方,则给定区间内并非所有可表示的浮点数都可能被返回。步进将从绝对值较大的区间边界开始,以确保步长与精确可表示的浮点数对齐。
闭区间边界将始终包含在可选浮点数集中。因此,如果区间的尺寸不是步长的精确倍数,并且绝对值较小的边界是闭边界,则该边界与其最接近的可选择浮点数之间的距离将小于步长。
对返回的浮点数进行后处理可能会破坏均匀等分布,因为数学运算中的中间浮点数会经历隐式舍入。请求的区间应尽可能地匹配所需的区间,并且舍入应仅作为显式操作在将选定的数字显示给用户之前执行。
为了举例说明算法的工作原理,请考虑一个使用 3 位尾数的浮点表示。这种表示能够表示连续的 2 的幂次方之间的 8 个不同的浮点值。这意味着在 1.0
和 2.0
之间,所有大小为 0.125
的步长都是精确可表示的,而在 2.0
和 4.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.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
)处是闭合的,并且闭合边界始终包含在内。
最后,算法随机地从四个可选值中均匀选取一个并返回它。
在前面的示例中,在由 2 的幂次方分隔的每个子区间之间有 8 个可表示的浮点数。为了举例说明为什么将两个整数相除不能很好地生成随机浮点数,请考虑在从 0.0
开始直到但不包括 1.0
的右开区间中有 16 个等分布的浮点数。其中一半是 0.5
和 1.0
之间 8 个精确可表示的值,另一半是在 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
此随机浮点数可以通过将其乘以区间的大小(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.0
和 2.25
返回的可能性是其他值的的两倍。
min
区间的下界。
max
区间的上界。
boundary
指定区间边界是否为可能的返回值。
由 min
、max
和 boundary
指定的区间中均匀选取的、等分布的浮点数。min
和 max
是否为可能的返回值取决于 boundary
的值。
min
的值不是有限的(is_finite()),则会抛出 ValueError。
max
的值不是有限的(is_finite()),则会抛出 ValueError。
Random\Randomizer::$engine
的 Random\Engine::generate() 方法抛出的任何 Throwable。
示例 #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 中的 γ-截面算法,以获得所需的行为属性。