从random . nextbytes()中创建具有最小值和最大值的随机整数

本文关键字:最小值 最大值 整数 随机 nextbytes random 创建 | 更新日期: 2023-09-27 18:07:36

标题已经说明了一切。当然,我知道我可以用Random.NextInt(),但我想知道是否有一种方法可以把无界随机数据变成有界而不存在统计偏差。(这意味着没有RandomInt() % (maximum-minimum)) + minimum)。肯定有一种类似的方法,它不会在输出的数据中引入偏见?

从random . nextbytes()中创建具有最小值和最大值的随机整数

如果你认为比特是随机分布的,我建议:

  • 生成足够的字节来获取范围内的数字(例如1字节获取0-100范围内的数字,2字节获取0-30000范围内的数字等)。
  • 从这些字节中只使用足够的位来覆盖您需要的范围。例如,如果要生成0-100范围内的数字,则取生成的字节的最后7位
  • 将您获得的位解释为[0,2 n)范围内的数字,其中n是位
  • 的数量
  • 检查号码是否在您想要的范围内。它应该至少是一半的时间(平均)
  • 如果是,使用它。如果没有,重复上述步骤,直到数字在正确的范围内。

只使用所需的比特数是提高效率的关键——您将浪费最多生成的字节数的一半,但不会超过这个数,假设分布良好。(如果你生成的数字是一个很好的二进制范围,你不需要扔掉任何东西。)

实现留给读者作为练习:)

您可以尝试这样做:

public static int MyNextInt(Random rnd, int minValue, int maxValue)
{
    var buffer = new byte[4];
    rnd.NextBytes(buffer);
    uint num = BitConverter.ToUInt32(buffer, 0);
    // The +1 is to exclude the maxValue in the case that 
    // minValue == int.MinValue, maxValue == int.MaxValue
    double dbl = num * 1.0 / ((long)uint.MaxValue + 1);
    long range = (long)maxValue - minValue;
    int result = (int)(dbl * range) + minValue;
    return result;
}

完全未经考验的……我不能保证结果真的是伪随机的。但是创建double (dbl)号的想法与Random类使用的相同。只有我使用uint.MaxValue作为基础,而不是int.MaxValue。这样,我就不必检查buffer的负值了。

我提出了一个基于NextBytes的随机整数生成器。由于使用Int64作为位操作的表示,该方法在字长范围内平均只丢弃9.62%的位。

最大位丢失发生在字长为22位时,在字节范围转换中使用64的20位丢失。在这种情况下,钻头效率为68.75%

此外,由于将未绑定范围剪切为最大值,25%的值丢失。

注意对返回的IEnumerable使用Take(N),否则它是一个无限生成器。

我使用512个长值的缓冲区,因此它一次生成4096个随机字节。如果您只需要一个由几个整数组成的序列,请将缓冲区大小从512更改为更优的值,降低到1。

public static class RandomExtensions
{
    public static IEnumerable<int> GetRandomIntegers(this Random r, int max)
    {
        if (max < 1)
            throw new ArgumentOutOfRangeException("max", max, "Must be a positive value.");
        const int longWordsTotal = 512;
        const int bufferSize = longWordsTotal * 8;
        var buffer = new byte[bufferSize];
        var wordSize = (int)Math.Log(max, 2) + 1;
        while(true)
        {
            r.NextBytes(buffer);
            for (var longWordIndex = 0; longWordIndex < longWordsTotal; longWordIndex++)
            {
                ulong longWord = BitConverter.ToUInt64(buffer, longWordIndex);
                var lastStartBit = 64 -  wordSize;
                var count = 0;
                for (var startBit = 0; startBit <= lastStartBit; startBit += wordSize)
                {
                    count ++;                       
                    var mask = ((1UL << wordSize) - 1) << startBit;                 
                    var unboundValue = (int)((mask & longWord) >> startBit);
                    if (unboundValue <= max)
                        yield return unboundValue;
                }
            }
        }       
    }
}