I'm trying to generate a completely random float
value, which should be pretty easy. However, it doesn't work, and by the looks of it maybe I'm the first person to come across this error ? I certainly can't seem to find any prior post about it.
This code :
let min = -Float.greatestFiniteMagnitude
let max = +Float.greatestFiniteMagnitude
let range = min...max
let random = Float.random(in: range)
Works until the last line, which fails, producing Swift/FloatingPointRandom.swift:162: Fatal error: There is no uniform distribution on an infinite range
However, the range shouldn't be infinite. Inspecting min and max or the range itself in the playground shows that the bounds are -3.402823e+38
to 3.402823e+38
, neither of which are infinite.
What is going on ? And is there a proper way to do this that I missed ? Besides the following, which seems very convoluted for such a simple task :
let min = Float(0)
let max = Float.greatestFiniteMagnitude
let range = min...max
let signs : [Float] = [-1, 1]
let random = Float.random(in: range) * signs.randomElement()!
This is a limitation of the current implementation of random number generation for floating point values — if you check out stdlib/public/core/FloatingPointRandom.swift:156
(right around where your error message points to), you can see where this limitation comes from:
let delta = range.upperBound - range.lowerBound
// TODO: this still isn't quite right, because the computation of delta
// can overflow (e.g. if .upperBound = .maximumFiniteMagnitude and
// .lowerBound = -.upperBound); this should be re-written with an
// algorithm that handles that case correctly, but this precondition
// is an acceptable short-term fix.
_precondition(
delta.isFinite,
"There is no uniform distribution on an infinite range"
)
The code attempts to prevent generating random numbers if the bounds aren't valid, but doesn't adequately handle overflows. There's a GitHub issue tracking this bug, but it appears that it's been around for a while now — although there's a comment stating that this would be fixed for Swift 5, that fix hasn't been introduced.
It may be worth commenting on that issue, or raising this on the Swift forums, where engineers who could address the issue can see this.
In the meantime, your proposed solution should get you close enough, for some definition of "close enough": you'll get a random number within the range that you want, though the limitations of floating-point precision may affect the exact distribution of numbers you get out. Depending on your use-case, this may not really matter, but without knowing more, this may or may not work for you until a full fix is introduced.
You could also attempt to generate a random number within a much larger range than [-1, 1]
, and scale it by a smaller amount, which may get you a more accurate distribution of values.