Welcome to Atalasoft Community Sign in | Help

Weighted Random Numbers

Maybe it’s all how many of the geeky kids in my generation played D&D, but there’s something about random numbers that brings out the geeks in droves.

I answered a question on stack overflow on how to weight random numbers more in a particular range.  Let’s discuss the math.

I’ll define a transform function T(x), where x is in the half-open range [0..1) (x may go from 0 up to but not including 1) such that for every x, y = T(x) is also in the same range.

The Random class in .NET has method, NextDouble, which returns a number conveniently in the range [0..1).  This means that if we apply some T(x) to NextDouble, we’ll get a number in the same range.

Based on that, I can write the following:

public abstract class DelegatedRandom
{
    private Random _r = new Random();
    public int Next(int low, int high)
    {
        if (high >= low)
            throw new ArgumentOutOfRangeException("high");
        double rand = _r.NextDouble();
        rand = Transform(rand);
        if (rand >= 1.0 || rand < 0) throw new Exception("internal error - expected transform to be between 0 and 1");
        return (int)((high - low) * rand) + low;
    }
    protected abstract Func<double, double> Transform { get; }
}

T(x) is built into the abstract property Transform, which is a Func<double, double>.  Transform returns a function that when applied to a double returns a double.  At runtime, I range check the results.

This lets me define subclasses that simply implement Transform.  For example, consider using exponentiation.  When the exponent is in the range [0..1), raising x to the exponent also yields a number in [0..1).  This is essentially gamma correction as used in image processing (by the way, dotImage has a GammaCommand to do just this image transform).  A gamma adjusted version of Random would look like this:

public class GammaRandom : DelegatedRandom
{
    private double _gamma;
    public GammaRandom(double gamma)
    {
        if (gamma <= 0) throw new ArgumentOutOfRangeException("gamma");
        _gamma = gamma;
    }
    protected override Func<double, double> Transform
    {
        get { return r => Math.Pow(r, _gamma); }
    }
}

If gamma is < 1, the weighting is greater on the lower end.  If gamma > 1, the weighting is more on the upper end.  You can also do this with trig functions.  Sin(x) in the range [0..π/2) returns a value in the range [0..1), so we can use that as well:

public class SinRandom : DelegatedRandom
{
    private static double pihalf = Math.PI / 2;
    protected override Func<double, double> Transform
    {
        get { return r => Math.Sin(r * pihalf); }
    }
}

DelegatedRandom gives you a nice little tool to play with random distributions.  Gaussian, anyone?

Published Friday, July 31, 2009 9:52 AM by Steve Hawley

Comments

No Comments
Anonymous comments are disabled