开发者

Generation of (pseudo) random constrained values of (U)Int64 and Decimal

Note: For brevity's sake, the following will not discern between randomness and pseudo-randomness. Also, in this context, constrained means between given min and max values)

The System.Random class provides random generation of integers, doubles and byte arrays. Using Random.Next, one can easily generate random constrained values of type Boolean, Char, (S)Byte, (U)Int16, (U)Int32. Using Random.NextDouble(), one can similarly generate constrained开发者_如何学Go values of types Double and Single (as far as my understanding of this type goes). Random string generation (of a given length and alphabet) has also been tackled before.

Consider the remaining primitive data types (excluding Object): Decimal and (U)Int64. Their random generation has been tackled as well (Decimal, (U)Int64 using Random.NextBytes()), but not when constrained. Rejection sampling (i.e. looping until the generated value is the desired range) could theoretically be used, but it is obviously not a practical solution. Normalizing NextDouble() won't work because it doesn't have enough significant digits.

In short, I am asking for the proper implementation of the following functions:

long NextLong(long min, long max)
long NextDecimal(decimal min, decimal max)

Note that, since System.DateTime is based on a ulong, the first function would allow for random constrained generation of such structs as well (similar to here, only in ticks instead of minutes).


This should do it. For decimal I utilized Jon Skeet's initial approach to generating random decimals (no constraints). For long I provided a method to produced random non-negative longs which is then used to create the a value in the random range.

Note that for decimal the resulting distribution is not a uniform distribution on [minValue, maxValue]. It merely is uniform on all the bit representations of decimals that fall in the range [minValue, maxValue]. I do not see an easy way around this without using rejection sampling.

For long the resulting distribution is uniform on [minValue, maxValue).

static class RandomExtensions {
    static int NextInt32(this Random rg) {
        unchecked {
            int firstBits = rg.Next(0, 1 << 4) << 28;
            int lastBits = rg.Next(0, 1 << 28);
            return firstBits | lastBits;
        }
    }

    public static decimal NextDecimal(this Random rg) {
        bool sign = rg.Next(2) == 1;
        return rg.NextDecimal(sign);
    }

    static decimal NextDecimal(this Random rg, bool sign) {
        byte scale = (byte)rg.Next(29);
        return new decimal(rg.NextInt32(),
                           rg.NextInt32(),
                           rg.NextInt32(),
                           sign,
                           scale);
    }

    static decimal NextNonNegativeDecimal(this Random rg) {
        return rg.NextDecimal(false);
    }

    public static decimal NextDecimal(this Random rg, decimal maxValue) {
        return (rg.NextNonNegativeDecimal() / Decimal.MaxValue) * maxValue; ;
    }

    public static decimal NextDecimal(this Random rg, decimal minValue, decimal maxValue) {
        if (minValue >= maxValue) {
            throw new InvalidOperationException();
        }
        decimal range = maxValue - minValue;
        return rg.NextDecimal(range) + minValue;
    }

    static long NextNonNegativeLong(this Random rg) {
        byte[] bytes = new byte[sizeof(long)];
        rg.NextBytes(bytes);
        // strip out the sign bit
        bytes[7] = (byte)(bytes[7] & 0x7f);
        return BitConverter.ToInt64(bytes, 0);
    }

    public static long NextLong(this Random rg, long maxValue) {
        return (long)((rg.NextNonNegativeLong() / (double)Int64.MaxValue) * maxValue);
    }

    public static long NextLong(this Random rg, long minValue, long maxValue) {
        if (minValue >= maxValue) {
            throw new InvalidOperationException();
        }
        long range = maxValue - minValue;
        return rg.NextLong(range) + minValue;
    }
}


Let's assume you know how to generate N random bits. This is pretty easily done either using NextBytes or repeated calls to Random.Next with appropriate limits.

To generate a long/ulong in the right range, work out how large the range is, and how many bits it takes to represent it. You can then use rejection sampling which will at worst reject half the generated values (e.g. if you want a value in the range [0, 128], which means you'll generate [0, 255] multiple times). If you want a non-zero based range, just work out the size of the range, generate a random value in [0, size) and then add the base.

Generating a random decimal is signficantly harder, I believe - aside from anything else, you'd have to specify the distribution you wanted.


I came here looking for a way to generate 64 bit values within an arbitrary range. The other answers failed to produce a random number when given certain ranges (e.g. long.MinValue to long.MaxValue). Here's my version that seems to solve the problem:

public static long NextInt64(this Random random, long minValue, long maxValue)
{
    Contract.Requires(random != null);
    Contract.Requires(minValue <= maxValue);
    Contract.Ensures(Contract.Result<long>() >= minValue &&
                     Contract.Result<long>() < maxValue);

    return (long)(minValue + (random.NextUInt64() % ((decimal)maxValue - minValue)));
}

It uses the following extension Methods:

public static ulong NextUInt64(this Random random)
{
    Contract.Requires(random != null);

    return BitConverter.ToUInt64(random.NextBytes(8), 0);
}

public static byte[] NextBytes(this Random random, int byteCount)
{
    Contract.Requires(random != null);
    Contract.Requires(byteCount > 0);
    Contract.Ensures(Contract.Result<byte[]>() != null &&
                     Contract.Result<byte[]>().Length == byteCount);

    var buffer = new byte[byteCount];
    random.NextBytes(buffer);
    return buffer;
}

The distribution is not perfectly even when the size of the requested range is not a clean divisor of 2^64, but it at least provides a random number within the request range for any given range.


Based upon Jon Skeet's method, here's my stab at it:

public static long NextLong(this Random rnd, long min, long max) 
{
    if (max <= min) 
    {
        throw new Exception("Min must be less than max.");
    }

    long dif = max - min;

    var bytes = new byte[8];
    rnd.NextBytes(bytes);
    bytes[7] &= 0x7f; //strip sign bit

    long posNum = BitConverter.ToInt64(bytes, 0);
    while (posNum > dif)
    {
        posNum >>= 1;
    }

    return min + posNum;
}

Let me know if you see any errors.


long posNum = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0); 


 use this instead of NextBytes
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜