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.04.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)的隨機浮點數。演算法首先確定絕對值較大 (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 的次方劃分的每個子區間之間,有八個可表示的浮點數。為了說明為什麼兩個整數相除無法很好地生成隨機浮點數,請考慮在從 0.0 到但不包含 1.0 的右開區間中,有 16 個等距分佈的浮點數。其中一半是 0.51.0 之間的八個可精確表示的值,另一半是 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

這個隨機浮點數可以縮放到從 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.02.25 被返回的可能性是其他值的兩倍。

參數

最小值

區間的下限。

最大值

區間的上限。

邊界

指定區間邊界是否為可能的返回值。

返回值

從由 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 中的 γ-section 演算法,以獲得所需的行為特性。

另請參閱

新增註釋

使用者貢獻的註釋

此頁面尚無使用者貢獻的註記。
To Top