【问题标题】:Generation of (pseudo) random constrained values of (U)Int64 and Decimal(U)Int64 和 Decimal 的(伪)随机约束值的生成
【发布时间】:2011-01-01 07:13:14
【问题描述】:

注意:为简洁起见,以下内容将不区分随机性和伪随机性。此外,在这种情况下,受约束表示在给定的最小值和最大值之间)

System.Random 类提供整数、双精度和字节数组的随机生成。 使用 Random.Next,可以轻松生成 Boolean、Char、(S)Byte、(U)Int16、(U)Int32 类型的随机约束值。使用Random.NextDouble(),可以类似地生成 Double 和 Single 类型的约束值(据我对这种类型的理解)。随机字符串生成(给定长度和字母)hasalsobeentackledbefore

考虑剩余的原始数据类型(不包括 Object):Decimal 和 (U)Int64。它们的随机生成也得到了解决(Decimal(U)Int64 使用Random.NextBytes()),但在受到约束时没有。理论上可以使用拒绝采样(即循环直到生成的值是所需范围),但这显然不是一个实际的解决方案。规范化 NextDouble() 不起作用,因为它没有足够的有效数字。

简而言之,我要求正确实现以下功能:

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

请注意,由于System.DateTime 基于 ulong,第一个函数也允许随机约束生成此类结构(类似于here,仅以滴答而不是分钟为单位)。

【问题讨论】:

    标签: c# random


    【解决方案1】:

    假设您知道如何生成 N 个随机位。这很容易通过使用NextBytes 或以适当的限制重复调用Random.Next 来完成。

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

    我相信,生成随机小数要困难得多 - 除了其他任何事情之外,您还必须指定您想要的分布。

    【讨论】:

    • 真的有那么难吗?我一定遗漏了一些东西,因为我的第一个想法是使用Random.NextBytes 并使用按位运算将它们应用于适当的十进制位以获得0到1之间的值。
    【解决方案2】:

    应该这样做。对于十进制,我利用 Jon Skeet 的初始方法生成随机 decimals(无约束)。对于long,我提供了一种生成随机非负longs 的方法,然后使用该方法在随机范围内创建a 值。

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

    对于long,生成的分布在[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;
        }
    }
    

    【讨论】:

    • 谢谢,这是一个很棒的 sn-p (+1) - 不过有几个问题: 1. 你不会在 rg.NextNonNegativeLong() / (double)Int64.MaxValue 中丢失信息吗?铸件失去了精度,并且划分可能无法完全表现——这些因素不会影响均匀性和属性吗? NextNonNegativeDecimal() / Decimal.MaxValue 2 也是如此。在 NextInt32 中,选择 4 和 28 有什么原因吗?任何两个加到 32 的正数(例如 16 + 16)都应该有效,对吧?谢谢!
    • 此代码不能提供有符号长。调用.NextLong(long.MinValue, long.MaxValue) 总是返回-9223372036854775808
    • NextDecimal(decimal.MinValue, 5) 会抛出 OverflowException
    【解决方案3】:

    根据 Jon Skeet 的方法,这是我的尝试:

    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;
    }
    

    如果您发现任何错误,请告诉我。

    【讨论】:

    • 通过将一个非常大的值除以 2 的幂来减少它会以可预测的方式缩小数字。最好是删除更重要的位,而不是更少。尝试将 1 掩码向右移位,直到值和掩码的按位与在范围内。你会得到更好的随机值分布。
    【解决方案4】:
    long posNum = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0); 
    
    
     use this instead of NextBytes
    

    【讨论】:

    • -1。 GUID 不是完全随机的。不应使用它们来代替伪随机数生成器。
    【解决方案5】:

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

    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)));
    }
    

    它使用以下扩展方法:

    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 的净除数,分布也不完美,但它至少为任何给定范围提供了请求范围内的随机数。

    【讨论】:

      猜你喜欢
      • 2016-08-25
      • 1970-01-01
      • 2012-04-06
      • 2016-05-06
      • 2015-08-07
      • 2012-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多