views:

280

answers:

5

What is the best way to constrain the values of a PRNG to a smaller range? If you use modulus and the old max number is not evenly divisible by the new max number you bias toward the 0 through (old_max - new_max - 1). I assume the best way would be something like this (this is floating point, not integer math)

random_num = PRNG() / max_orginal_range * max_smaller_range

but something in my gut makes me question that method (maybe floating point implementation and representation differences?).

The random number generator will produce consistent results across hardware and software platforms, and the constraint needs to as well.

I was right to doubt the pseudocode above (but not for the reasons I was thinking). MichaelGG's answer got me thinking about the problem in a different way. I can model it using smaller numbers and test every outcome. So, let's assume we have a PRNG that produces a random number between 0 and 31 and you want the smaller range to be 0 to 9. If you use modulus you bias toward 0, 1, 2, and 3. If you use the pseudocode above you bias toward 0, 2, 5, and 7. I don't think there can be a good way to map one set into the other. The best that I have come up with so far is to regenerate the random numbers that are greater than old_max/new_max, but that has deep problems as well (reducing the period, time to generate new numbers until one is in the right range, etc.).

I think I may have naively approached this problem. It may be time to start some serious research into the literature (someone has to have tackled this before).

+2  A: 

I know this might not be a particularly helpful answer, but I think the best way would be to conceive of a few different methods, then trying them out a few million times, and check the result sets.

When in doubt, try it yourself.

EDIT

It should be noted that many languages (like C#) have built in limiting in their functions

int maximumvalue = 20;
Random rand = new Random();

rand.Next(maximumvalue);

And whenever possible, you should use those rather than any code you would write yourself. Don't Reinvent The Wheel.

You can also take a look at java.util.Random.nextInt(int) which uses a pretty clever method of constraining the result without introducing bias. Took me about a day to understand why it works, though :)
Joey
Where is that source available (Sorry, I'm not a java coder, I don't know anything about where the API is)
Random checking isn't a good idea, but if I reduce the numbers to something manageable I can test every outcome (see above), and the pseudocode is in fact biased. Now I have to go digging through articles I am unlikely to understand, sigh.
Chas. Owens
A: 

If PRNG() is generating uniformly distributed random numbers then the above looks good. In fact (if you want to scale the mean etc.) the above should be fine for all purposes. I guess you need to ask what the error associated with the original PRNG() is, and whether further manipulating will add to that substantially.

If in doubt, generate an appropriately sized sample set, and look at the results in Excel or similar (to check your mean / std.dev etc. for what you'd expect)

Brian Agnew
A: 

If you have access to a PRNG function (say, random()) that'll generate numbers in the range 0 <= x < 1, can you not just do:

 random_num = (int) (random() * max_range);

to give you numbers in the range 0 to max_range?

Dana
The PRNG in question generates a number between 0 and 2^32, and even if it did I am still worried about floating point inaccuracy causing problems between systems (e.g. 1/10 cannot be represented accurately, so some implementations choose .09...9 and others .10...01)
Chas. Owens
A: 

Here's how the CLR's Random class works when limited (as per Reflector):

long num = maxValue - minValue;
if (num <= 0x7fffffffL) {
    return (((int) (this.Sample() * num)) + minValue);
}
return (((int) ((long) (this.GetSampleForLargeRange() * num))) + minValue);

Even if you're given a positive int, it's not hard to get it to a double. Just multiply the random int by (1/maxint). Going from a 32-bit int to a double should provide adequate precision. (I haven't actually tested a PRNG like this, so I might be missing something with floats.)

MichaelGG
Hmm, if I am reading that right the PRNG is generating a float (if) or a double (outside if). Since PRNGs don't produce integers this is most likely real_random/(double)MAX_RANDOM. So the only difference is my pseudo code assumes a zero base and this allows an arbitrary base.
Chas. Owens
If you inspect the Random class you'll see that internally ("InternalSample") it generates an int, then multiplies by 1/Int32.MaxValue (as a double). GetSampleForLarge range just allows it with a full int32 range.
MichaelGG
So, InternalSample is the real_random and multiplying by a reciprocal is the same as dividing (there must be a difference in the efficiency). That means it is biasing the results when the 2^32 is not evenly divisible by the new range. It is definitely sounding like there is no way around the bias.
Chas. Owens
Well, am I missing something, or could you not simply use more random ints to push the precision high enough that the bias is not a practical concern? I.e., if your actual new range is 2^30+something, use 2x32-bit ints, or even the 128-bit decimals?
MichaelGG
Bias is probably not a practical concern now. Take the normal case: a 32bit PRNG constrained to 0-9. You have a 2e8% higher chance of getting 0-5 than 6-9. What I am doing that brought this up is moding a 2^32 number with 2^8, which has no bias. It was just a loose thread I started pulling.
Chas. Owens
A: 

Psuedo random number generators are essentially producing a random series of 1s and 0s, which when appended to each other, are an infinitely large number in base two. each time you consume a bit from you're prng, you are dividing that number by two and keeping the modulus. You can do this forever without wasting a single bit.

If you need a number in the range [0, N), then you need the same, but instead of base two, you need base N. It's basically trivial to convert the bases. Consume the number of bits you need, return the remainder of those bits back to your prng to be used next time a number is needed.

TokenMacGuy
Either I don't understand what you are getting at or it still is biased. Assume a PRNG that generates 0 - 3, we want to reduce it to 0 - 2. We want four numbers. It is easy to model every case. Adding up the results should be equal for 0 - 2. We have to throw away the pattern 11 or it baises to 1
Chas. Owens