Really divisionless random numbers
dotat.at
dotat.at
(Sadly the common flavour of EPP has a bonkers data model; a few ccTLDs use an alternative model with a more sensible data model, but although the more sensible flavour of EPP is easier to use in isolation, it has the downside of being different from the ICANN flavour.)
> a little bit eccentric
Stereotype checks out :D
Is it .at blocking the nameserver changes or AWS refusing to serve .at domains from their nameservers?
Find the largest multiple of your limit which is less than W. We want to find a random number less than this multiple, because then we can just divide down to get an unbiased result. Maybe this is already implied in how these algorithms are set up, not sure.
Generate a candidate random number. If it is smaller than the multiplied limit, you are done. If not, double it, and add a random bit. Repeat until success.
To supply the random bits, generate a random number, and use it up one bit at a time. If you need more, generate another random number.
I suppose this doesn't work, because the second-most-significant bit of a rejected candidate isn't unbiased. If the multiplied limit is 3/4 of W, then we only reject candidates where the top two bits are both 1!
Can you rescue this by discarding more than one bit when you reject?
Probably not. Think about the case where the multiplied limit is W-1. The only candidate you would reject is all 1s. You'd have to discard all the bits for the next candidate to be unbiased.
Oh well.
The only time I hear about 'other ways' is here on Hacker News.
edit: range notation
Really? I would have expected at least a 20:1 ratio in favor of [0,1) over (0,1].
Most obviously, (0,1] can't produce a uniform distribution, because the input value 1 produces its own unique output value. Whereas the input value 0 shares its output value with the rest of the interval (0, 1/n).
My point is you're multiplying a float, not dividing an integer.
That's not so easy to do; I suspect the fancier methods of generating random numbers are of interest because they have an easier requirement such as "32 random bits".
It's plenty easy to do. Start with 1.0 then fill the mantissa with random bits. For a flat distribution subtract 1.0. For an "every float is possible" distribution count the leading 0 bits in another chunk of random, and subtract count+1 from the exponent.
You'd probably want the former for a situation like this.
Java, Javascript, Python, Ruby, Perl
languages where it's optional:
C#, Elixir, Go
That's about when I got tired of Googling.
Random also exists, with a bunch of options including CSPRNG. But if you’re just simulating a die roll, Math.random is sufficient unless you’re writing gambling software.
Java: Random.nextInt(b)
Python: random.randrange(b)
Ruby: rand(b)
Those examples are all relatively low-level. Many high-level languages provide a floating point interface on top of such an underlying integer pseudo randomness algorithm. This is the reason why much high-level code use multiplication to get a ranged value.
This post goes into some detail about how the V8 JavaScript engine creates a [0, 1) floating point pseudo random value from its underlying integer pseudo-random algorithm: https://v8.dev/blog/math-random
You'd think so but it doesn't really say. It's almost entirely about how they make the integer.
Following the commit link shows that they use the method of filling 1.0 with random mantissa bits and then subtracting 1.0
>> The number of random values it can generate is limited to 2^32 as opposed to the 2^52 numbers between 0 and 1 that double precision floating point can represent.
Not really true. That's how many numbers their algorithm returns, which is almost as many evenly-spaced floats there are between 0 and 1. But because it starts with a number between 1 and 2, that method actually wastes a bit, and that number really should be 2^53.
But floating point itself has 1/4 of its values between 0 and 1, so for double precision that's roughly 2^62!
Well, yes, but a discrete/integer-ish version. Every number is equidistant and equally likely.
No, there are 2^62 - 2^52 float64 values in [0, 1), so your method (which I assume is to fix the exponent at 0, randomize mantissa so we get 1.abcdef... and subtract 1) can't possible be sensible, if your sense of sensibility includes correctness.
First note that the only correct way to generate a floating point in [a, b] is to generate a random number in [a, b] with infinite precision and then round it to the closest floating point number in that range. This maintains all the nice properties of real uniform generation (assuming a, b are representable floating point numbers), expectation of (a + b) / 2, continuity of ranges such that generating U[a, c] is the same as choosing between U[a, b] and U[b, c] with weights (b-a) and (c-b) respectively, etc, while having an non-zero chance of generating any floating point value in [a, b].
An efficient algorithm for generating a float in [0, 1] is described in "Generating Pseudo-random Floating-Point Values" by Allen B. Downey. Correctly implemented it uses a handful of instructions and single call to the word-sized random number generator > 99% of the time, using a count-trailing-zeros (or similar) CPU instruction to efficiently sample from an exponential distribution (as opposed to the naive loop in the paper).
Note that the mathematical distributions U[0, 1) and U[0, 1] are indistinguishable. So when we say "a uniform float in the range [0, 1)" we can't possibly mean U[0, 1). What we mean in reality is something that approximates U[0, 1] but can't generate the floating point value 1.0. One decent way to do that is to sample from U[0, 1] but reject the 1.0 output. This effectively samples from U[0, 1 - eps] where eps = (1 - floatbefore(1.0)) / 2.
This unfortunately does mean we lose our nice expectation of 0.5. Note that the error in expectation for sampling from [1, 2) using strictly mantissa and subtracting 1 is significantly worse. For example in a 2-bit exponent 2-bit mantissa unsigned floating point with exponent bias -1 (giving the following list of values)
0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 3.5
we find that sampling using Allen B. Downey's method from [0.0, 1.0] but rejecting 1.0 as output has an expectation of 0.46875, sampling uniformly from {1.0, 1.25, 1.5, 1.75} and subtracting 1 like the mantissa method would have an expectation of 0.375.I think this is likely due to the nature of JavaScript’s whacky integer and float implementations.