Description
Due to the limited precision, not all real numbers can be exactly represented as
a floating point number.
If a number cannot be represented exactly, it is rounded to the nearest
representable exact value.
Furthermore, floats are not equally dense across the whole number line.
Because floats use a binary exponent, the distance between two neighboring
floats doubles at each power of two.
In other words: There are the same number of representable floats between
1.0 and 2.0
as they are between
2.0 and 4.0,
4.0 and 8.0,
8.0 and 16.0,
and so on.
Randomly sampling an arbitrary number within the requested interval, for example
by dividing two integers, might result in a biased distribution for this reason.
The necessary rounding will cause some floats to be returned more often than
others, especially around powers of two when the density of floats changes.
Random\Randomizer::getFloat() implements an algorithm that
will return a uniformly selected float from the largest possible set of exactly
representable and equidistributed floats within the requested interval.
The distance between the selectable floats (“step size”) matches the distance
between the floats with the lowest density, i.e. the distance between floats
at interval boundary with the larger absolute value.
This means that not all representable floats within a given interval may be
returned if the interval crosses one or more powers of two.
Stepping will start from the interval boundary with the larger absolute value
to ensure the steps align with the exactly representable floats.
Closed interval boundaries will always be included in the set of selectable
floats.
Thus, if the size of the interval is not an exact multiple of the step size and
the boundary with the smaller absolute value is a closed boundary, the distance
between that boundary and its nearest selectable float will be smaller than the
step size.
Caution
Post-processing the returned floats is likely going to break the uniform
equidistribution, because the intermediate floats within a mathematical
operation are experiencing implicit rounding.
The requested interval should match the desired interval as closely as possible
and rounding should only be performed as an explicit operation right before
displaying the selected number to a user.
Explanation of the Algorithm Using Example Values
To give an example of how the algorithm works, consider a floating point
representation that uses a 3-bit mantissa.
This representation is capable of representing 8 different floating point
values between consecutive powers of two.
This means that between
1.0 and 2.0 all steps of size 0.125
are exactly representable and between 2.0 and 4.0
all steps of size 0.25 are exactly representable.
In reality PHP’s floats use a 52-bit mantissa and can represent 252
different values between each power of two.
This means that
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
are the exactly representable floats between
1.0
and
4.0.
Now consider that $randomizer->getFloat(1.625, 2.5, IntervalBoundary::ClosedOpen)
is called, i.e. a random float starting at 1.625 until, but not including,
2.5 is requested.
The algorithm first determines the step size at the boundary with the larger absolute
value (2.5). The step size at that boundary is 0.25.
Note that the size of the requested interval is 0.875, which is
not an exact multiple of 0.25.
If the algorithm would start stepping at the lower bound 1.625, it
would encounter 2.125, which is not exactly representable and would
experience implicit rounding.
Thus the algorithm starts stepping at the upper boundary 2.5.
The selectable values are:
2.5 is not included, because the upper boundary of the requested
interval is an open boundary.
1.625 is included, even though its distance to the nearest value
1.75 is
0.125, which is smaller than the
previously determined step size of
0.25.
The reason for that is that the requested interval is closed at the lower boundary
(
1.625) and closed boundaries are always included.
Finally the algorithm uniformly selects one of the four selectable values at random
and returns it.