(PHP 8 >= 8.3.0)
Random\Randomizer::getFloat — 获取均匀选择的浮点数
$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 位尾数,并且可以在每个二的幂之间表示 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
这个随机浮点数可以通过将其乘以区间的长度 (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
注意:
此方法实现了 γ-section 算法,如 » 从区间中提取随机浮点数。 Frédéric Goualard, ACM Trans. Model. Comput. Simul., 32:3, 2022 中所述,以获得所需的特性。