5

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 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).

Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198

5 Answers5

9

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;
    }
}
jason
  • 236,483
  • 35
  • 423
  • 525
  • Thanks, this is a great snippet (+1) - a couple of questions though: 1. Wouldn't you lose information in rg.NextNonNegativeLong() / (double)Int64.MaxValue? The cast loses precision and the division might not be fully representable - wouldn't these factors affect the uniformity and onto properties ? The same regarding NextNonNegativeDecimal() / Decimal.MaxValue 2. In NextInt32, is there any reason 4 and 28 were selected? any two positive numbers that add to 32 (e.g. 16 + 16) should would work, right? Thanks ! – Ohad Schneider Jan 05 '10 at 09:16
  • 1
    This code can't provide a signed long. Calling `.NextLong(long.MinValue, long.MaxValue)` always returns `-9223372036854775808`. – Allon Guralnek Mar 15 '12 at 10:18
  • NextDecimal(decimal.MinValue, 5) will throw OverflowException – Danil Aug 22 '12 at 07:07
6

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Is it actually that hard? I must be missing something, because my first thought was to use `Random.NextBytes` and use bitwise operations to apply them to the appropriate decimal bits to get a value between 0 and 1. – Kyle Baran Aug 16 '15 at 20:09
1

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.

Allon Guralnek
  • 15,813
  • 6
  • 60
  • 93
0

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.

Hille
  • 2,123
  • 22
  • 39
JP Richardson
  • 38,609
  • 36
  • 119
  • 151
  • Reducing a very large value by dividing it by a power of 2 shrinks the number in a predictable fashion. It would be better instead to remove bits MORE significant, not less. Try bit-shifting a 1-mask to the right until the bitwise AND of the value and mask is within the range. You get a much better distribution of random values. – KeithS Feb 01 '12 at 00:58
-2
long posNum = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0); 


 use this instead of NextBytes
zing ming
  • 135
  • 1
  • 3