Random Integers from Random Integers

Or “Smaller Random Integers from Larger”. This topic is easy to overlook. It is also easy to implement badly.

One may have the best basic random number generator and yet have serious problems doing something with its result that looks easy. That “easy something” is the task of using the basic generator to produce random numbers in some other range.

In this discussion we assume that the basic generator has a larger range than the desired range. For example our base generator may return numbers in the range of 0 to 65535 yet we desire numbers in the range of 0 to 9. Using these numbers two obvious methods look like...

Method 1: Result = rand( ) % 10
Method 2: Result = int(rand( )*10/65536)

Both are wrong. Not by much, but still wrong. To show the problem assume that rand() returns number in the range of 0 to 7 and that we want a result in the range of 0 to 2.
 

rand() rand()%3 rand()*3/8
0 0 0
1 1 0
2 2 0
3 0 1
4 1 1
5 2 1
6 0 2
7 1 2

In each case, result=2 is underrepresented. A little thought will show that the mapping of any rand() with a range equal to some power of two in length into any range not a power of 2 in length will lead to this problem.

What to do. First be very careful about any use of method one. The basic Linear Congrential Generator has a serious flaw when its divisor is a power of 2 (often the case) and if the entire seed is returned. Method one could end up using only the low order “bad bits” of seed.

Use method two. Then think of the bits returned from rand() as being a binary fraction with the binary point on the left. Multiply by the result range. The integer portion of the product (the bits to the left of the binary point in the product) is often the desired result. Now ask “if I had all 1 bits to the right of the original rand() would the result change?” If the concatenation of hundreds of 1 bits to the original rand() could not change the integer portion of the product then we are done. Return the value to the left of the binary point. But if a mess of 1 bits could change the result then we have to get another rand() and figure it into the product. If in doing so the product carries into the integer portion of the original product then return that new integer portion. If not then repeat until either another rand() is shown to have no effect or it causes a carry into the original integer portion. Sounds hard. It aint.

To make the above a little simpler, first special case requested ranges equal to some power of 2. If the underlying generator allows, these specific values can be processed by simply masking rand() to the proper size. After these ranges are excluded, the test for possible overflow can be made by adding the range to the low bits of the product. This has the same effect as originally obtaining rand()+1. If that addition does not carry past the binary point then no amount of 1 bits can either.

All of the above is about 20 lines of 80x86 assembly code.

One other hint: use a simple rejection method for requested ranges above 70% of the range of the low level generator. In a C-like pseudo code our function overview becomes...

unsigned int randRange(unsigned int maxValue)
{
  unsigned int range = maxValue+1;
  if ((maxValue & range) == 0)
  return rand() & maxValue; //mask and be done
  if (maxValue > 40000)
  {
    unsigned int temp;
    do temp=rand() until (temp < range;); // rejection method
    return temp;
}
//
// return result from the 20 lines of assembly code
//
}


Next Topic (Short Sequence Generator)
Previous Topic
Return to index