5

There is known Random(0,1) function, it is a uniformed random function, which means, it will give 0 or 1, with probability 50%. Implement Random(a, b) that only makes calls to Random(0,1)

What I though so far is, put the range a-b in a 0 based array, then I have index 0, 1, 2...b-a.

then call the RANDOM(0,1) b-a times, sum the results as generated idx. and return the element.

However since there is no answer in the book, I don't know if this way is correct or the best. How to prove that the probability of returning each element is exactly same and is 1/(b-a+1) ?

And what is the right/better way to do this?

Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
Kent
  • 189,393
  • 32
  • 233
  • 301
  • Possible duplicate: [How to get random numbers with the wrong generator](http://stackoverflow.com/questions/7694933/how-to-get-random-numbers-with-the-wrong-generator) – PengOne Dec 10 '11 at 22:00

5 Answers5

12

If your RANDOM(0, 1) returns either 0 or 1, each with probability 0.5 then you can generate bits until you have enough to represent the number (b-a+1) in binary. This gives you a random number in a slightly too large range: you can test and repeat if it fails. Something like this (in Python).

def rand_pow2(bit_count):
    """Return a random number with the given number of bits."""
    result = 0
    for i in xrange(bit_count):
        result = 2 * result + RANDOM(0, 1)
    return result

def random_range(a, b):
    """Return a random integer in the closed interval [a, b]."""
    bit_count = math.ceil(math.log2(b - a + 1))
    while True:
        r = rand_pow2(bit_count)
        if a + r <= b:
            return a + r
  • 1
    This solution produces a uniform distribution by producing a uniform distribution over 0..(2^N-1) then discarding the result if it's out of the required range to construct a uniform distribution over a non-power of 2. Your solution constructs a binomial distribution. –  Dec 10 '11 at 22:00
  • i got it, your way is correct. but the result line would be `result += RANDOM(0,1) * 2 ** i` right? – Kent Dec 10 '11 at 22:12
  • Yes, you can construct it either way, or even result += RANDOM(0, 1) << i. –  Dec 10 '11 at 22:15
2

When you sum random numbers, the result is not longer evenly distributed - it looks like a Gaussian function. Look up "law of large numbers" or read any probability book / article. Just like flipping coins 100 times is highly highly unlikely to give 100 heads. It's likely to give close to 50 heads and 50 tails.

user949300
  • 15,364
  • 7
  • 35
  • 66
1

Divide and conquer could help us in generating a random number in range [a,b] using random(0,1). The idea is

  1. if a is equal to b, then random number is a
  2. Find mid of the range [a,b]
  3. Generate random(0,1)
  4. If above is 0, return a random number in range [a,mid] using recursion
  5. else return a random number in range [mid+1, b] using recursion

The working 'C' code is as follows.

int random(int a, int b)
{ 
    if(a == b)
        return a;

    int c = RANDOM(0,1); // Returns 0 or 1 with probability 0.5
    int mid = a + (b-a)/2;

    if(c == 0)
       return random(a, mid);
    else
       return random(mid + 1, b);
}
nstCactus
  • 5,141
  • 2
  • 30
  • 41
Bilal
  • 157
  • 1
  • 5
  • 2
    This will only result in a uniform distribution if all subranges are equal in size, which only happens if the range is a power of 2. It's mathematically equivalent to generating the bits, and you only get uniformity if you reject the bit-sets that are outside the range for ranges that aren't powers of 2. – pjs Jul 12 '14 at 05:29
1

Your inclination to put the range from 0 to a-b first is correct. However, you cannot do it as you stated. This question asks exactly how to do that, and the answer utilizes unique factorization. Write m=a-b in base 2, keeping track of the largest needed exponent, say e. Then, find the biggest multiple of m that is smaller than 2^e, call it k. Finally, generate e numbers with RANDOM(0,1), take them as the base 2 expansion of some number x, if x < k*m, return x, otherwise try again. The program looks something like this (simple case when m<2^2):

int RANDOM(0,m) {

    // find largest power of n needed to write m in base 2
    int e=0;
    while (m > 2^e) {
        ++e;
    }

    // find largest multiple of m less than 2^e
    int k=1;
    while (k*m < 2^2) {
        ++k
    }
    --k; // we went one too far

    while (1) {
        // generate a random number in base 2
        int x = 0;
        for (int i=0; i<e; ++i) {
            x = x*2 + RANDOM(0,1); 
        }
        // if x isn't too large, return it x modulo m
        if (x < m*k) 
            return (x % m);
    }
}

Now you can simply add a to the result to get uniformly distributed numbers between a and b.

Community
  • 1
  • 1
PengOne
  • 48,188
  • 17
  • 130
  • 149
0

If you have a RNG that returns {0, 1} with equal probability, you can easily create a RNG that returns numbers {0, 2^n} with equal probability.

To do this you just use your original RNG n times and get a binary number like 0010110111. Each of the numbers are (from 0 to 2^n) are equally likely.

Now it is easy to get a RNG from a to b, where b - a = 2^n. You just create a previous RNG and add a to it.

Now the last question is what should you do if b-a is not 2^n?


Good thing that you have to do almost nothing. Relying on rejection sampling technique. It tells you that if you have a big set and have a RNG over that set and need to select an element from a subset of this set, you can just keep selecting an element from a bigger set and discarding them till they exist in your subset.

So all you do, is find b-a and find the first n such that b-a <= 2^n. Then using rejection sampling till you picked an element smaller b-a. Than you just add a.

Salvador Dali
  • 214,103
  • 147
  • 703
  • 753