Let me reformulate 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 exactly 1/D greater than the previous one. Create a random number generator rand(L, U) with a discrete uniform distribution over [L, U) so that every possible value is exactly 1/D greater than the previous one.
-
A few quick notes.
- The problem is in this form, and, as you put it, it is insoluble. What if N = 1, we can do nothing.
- I do not require
0.0 be one of the possible values for random() . If this is not so, then it is possible that the solution below will not be satisfied if U - L < 1 / D It doesn't bother me much. - I use all half-open ranges because it simplifies the analysis. Using your closed ranges would be simple but tedious.
Finally, good stuff. The key understanding here is that density can be maintained by independently selecting the entire and partial part of the result.
First, note that given random() trivial to create randomBit() . I.e
randomBit() { return random() >= 0.5; }
Then, if we want to select one of {0, 1, 2, ..., 2^N - 1} uniformly randomly, just using randomBit() , just generate every bit. Call it random2(N) .
Using random2() , we can choose one of {0, 1, 2, ..., N - 1} :
randomInt(N) { while ((val = random2(ceil(log2(N)))) >= N); return val; }
Now, if D known, then the problem is trivial, since we can reduce it to a simple choice of one of the floor((U - L) * D) values uniformly randomly, and we can do this with randomInt() .
So, suppose that D unknown. Now let me first create a function to generate random values in the range [0, 2^N) with the proper density. It's simple.
rand2D(N) { return random2(N) + random(); }
rand2D() , where we require that the difference between consecutive possible values for random() be exactly 1/D If not, the possible values here will 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); }
Now we can shift discrete positions if L / D not an integer, but this is not essential.
-
In the last post, you may have noticed that some of these functions never stop. This is essentially a requirement. For example, random() can only have one bit of randomness. If I ask you to choose one of three values, you cannot do it evenly randomly with a guarantee that the function will complete.