关于c#:生成(U)Int64和Decimal的(伪)随机约束值

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

注:为了简洁起见,下面将不区分随机性和伪随机性。此外,在这种情况下,约束意味着在给定的最小值和最大值之间)

System.Random类提供整数、双精度和字节数组的随机生成。接下来,我们可以轻松地生成Boolean、Char、Byte、Int16、Int32类型的随机约束值。使用Random.NextDouble()可以类似地生成double和single类型的约束值(就我对这种类型的理解而言)。随机字符串生成(给定长度和字母表)也曾被处理过。

考虑剩下的基本数据类型(不包括对象):decimal和(u)int64。他们的随机生成也被处理了(十进制,(u)int64使用Random.NextBytes(),但没有受到限制。理论上可以使用拒绝抽样(即循环直到生成的值是所需的范围),但这显然不是一个实际的解决方案。规范化NextDouble()不会起作用,因为它没有足够的有效数字。

简而言之,我要求正确执行以下功能:

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

注意,由于System.DateTime基于一个ulong,所以第一个函数也允许随机约束生成这样的结构(类似于这里,只允许以滴答而不是分钟)。


这应该可以做到。对于decimal,我使用jon skeet的初始方法生成随机decimals(无约束)。对于long我提供了一种生成随机非负longs的方法,然后用它在随机范围内创建a值。

注意,对于decimal,所得分布在[minValue, maxValue]上不是均匀分布。它只不过是在[minValue, maxValue]范围内的所有小数位表示上的一致性。如果不使用拒绝抽样,我看不到一个简单的解决方法。

对于long,所得分布在[minValue, maxValue)上是均匀的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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;
    }
}


假设您知道如何生成n个随机位。使用NextBytes或重复调用Random.Next时,只要有适当的限制,就很容易做到这一点。

要在正确的范围内生成long/ulong,请计算范围有多大,以及表示该范围需要多少位。然后,您可以使用拒绝采样,这将最坏地拒绝生成值的一半(例如,如果您想要一个在范围[0,128]内的值,这意味着您将多次生成[0,255]。如果您想要一个非零的范围,只需计算出范围的大小,在[0,size]中生成一个随机值,然后添加基数。

生成一个随机的小数是非常困难的,我相信-除了其他的,你必须指定你想要的分布。


我来这里寻找一种在任意范围内生成64位值的方法。其他答案在给定范围(例如long.minvalue到long.maxvalue)时无法生成随机数。这是我的版本,似乎可以解决问题:

1
2
3
4
5
6
7
8
9
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)));
}

它使用以下扩展方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

即使请求范围的大小不是2^64的干净除数,分布也不完美,但它至少为任何给定范围提供了请求范围内的随机数。


根据乔恩·斯基特的方法,我想说的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

如果发现任何错误,请通知我。


1
2
3
4
long posNum = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0);


 use this instead of NextBytes