Let me rephrase your question:
Let random()
be a random number generator with a discrete uniform distribution over [0,1)
. Let D
be the number of possible values returned by random()
, each of which is precisely 1/D
greater than the previous. Create a random number generator rand(L, U)
with a discrete uniform distribution over [L, U)
such that each possible value is precisely 1/D
greater than the previous.
--
A couple quick notes.
- The problem in this form, and as you phrased it is unsolvable. That
is, if N = 1 there is nothing we can do.
- I don't require that
0.0
be one of the possible values for random()
. If it is not, then it is possible that the solution below will fail when U - L < 1 / D
. I'm not particularly worried about that case.
- I use all half-open ranges because it makes the analysis simpler. Using your closed ranges would be simple, but tedious.
Finally, the good stuff. The key insight here is that the density can be maintained by independently selecting the whole and fractional parts of the result.
First, note that given random()
it is trivial to create randomBit()
. That is,
randomBit() { return random() >= 0.5; }
Then, if we want to select one of {0, 1, 2, ..., 2^N - 1}
uniformly at random, that is simple using randomBit()
, just generate each of the bits. Call this random2(N)
.
Using random2()
we can select one of {0, 1, 2, ..., N - 1}
:
randomInt(N) { while ((val = random2(ceil(log2(N)))) >= N); return val; }
Now, if D
is known, then the problem is trivial as we can reduce it to simply choosing one of floor((U - L) * D)
values uniformly at random and we can do that with randomInt()
.
So, let's assume that D
is not known. Now, let's first make a function to generate random values in the range [0, 2^N)
with the proper density. This is simple.
rand2D(N) { return random2(N) + random(); }
rand2D()
is where we require that the difference between consecutive possible values for random()
be precisely 1/D
. If not, the possible values here would not have uniform density.
Next, we need a function that selects a value in the range [0, V)
with the proper density. This is similar to randomInt()
above.
randD(V) { while ((val = rand2D(ceil(log2(V)))) >= V); return val; }
And finally...
rand(L, U) { return L + randD(U - L); }
We now may have offset the discrete positions if L / D
is not an integer, but that is unimportant.
--
A last note, you may have noticed that several of these functions may never terminate. That is essentially a requirement. For example, random()
may have only a single bit of randomness. If I then ask you to select from one of three values, you cannot do so uniformly at random with a function that is guaranteed to terminate.