生成具有一定最大值的均匀随机整数

时间:2012-02-29 12:19:52

标签: c# algorithm random uniform

我想生成满足0 <= result <= maxValue的统一整数。

我已经有了一个生成器,它在内置的无符号整数类型的整个范围内返回统一值。我们来调用byte Byte()ushort UInt16()uint UInt32()ulong UInt64()的方法。假设这些方法的结果完全统一。

我想要的方法的签名是uint UniformUInt(uint maxValue)ulong UniformUInt(ulong maxValue)

我在寻找:

  1. 正确性
    我希望在给定的时间间隔内分配返回值 但是,如果显着提高性能,非常小偏差是可以接受的。我的意思是指一个订单的偏差,允许区分符的概率为2/3,给定2 ^ 64个值 它必须适用于任何maxValue
  2. 效果
    该方法应该很快。
  3. 效率
    该方法确实消耗很少的原始随机性,因为取决于底层生成器,生成原始字节可能是昂贵的。浪费几个比特很好,但是消耗128比特来生成一个数字可能是过多的。
  4. 还可以在某些成员变量中缓存前一次调用中的一些遗留随机性。

    小心int溢出和包装行为。

    我已经有了一个解决方案(我会将其作为答案发布),但这对我的口味来说有点难看。所以我想获得更好解决方案的想法。

    关于如何使用大maxValue进行单元测试的建议也不错,因为我无法生成具有2 ^ 64个桶和2 ^ 74个随机值的直方图。另一个复杂因素是,对于某些错误,只有一些maxValue分布偏差很大,而其他分布只有很小的偏差。

3 个答案:

答案 0 :(得分:2)

像这样的通用解决方案怎么样?该算法基于Java's nextInt method使用的算法,拒绝任何会导致非均匀分布的值。只要你的UInt32方法的输出完全一致,那么这也应该是。

uint UniformUInt(uint inclusiveMaxValue)
{
    unchecked
    {
        uint exclusiveMaxValue = inclusiveMaxValue + 1;

        // if exclusiveMaxValue is a power of two then we can just use a mask
        // also handles the edge case where inclusiveMaxValue is uint.MaxValue
        if ((exclusiveMaxValue & (~exclusiveMaxValue + 1)) == exclusiveMaxValue)
            return UInt32() & inclusiveMaxValue;

        uint bits, val;
        do
        {
            bits = UInt32();
            val = bits % exclusiveMaxValue;

            // if (bits - val + inclusiveMaxValue) overflows then val has been
            // taken from an incomplete chunk at the end of the range of bits
            // in that case we reject it and loop again
        } while (bits - val + inclusiveMaxValue < inclusiveMaxValue);

        return val;
    }
}

理论上,拒绝过程可以永远循环;在实践中,表现应该是相当不错的。如果不知道(a)预期使用模式,并且(b)基础RNG的性能特征,则很难建议任何普遍适用的优化。

例如,如果大多数呼叫者将指定最大值&lt; = 255,则每次要求四个字节的随机性可能没有意义。另一方面,总是检查实际需要的额外成本可能会超过请求更少字节的性能优势。 (当然,一旦你有特定的信息,那么你可以继续优化和测试,直到你的结果足够好。)

答案 1 :(得分:1)

我不确定,他的答案是肯定的。它绝对需要比评论更多的空间,所以我必须在这里写,但如果其他人认为这是愚蠢的话,我愿意删除。

从我得到的OQ,

  1. 熵位非常昂贵
  2. 其他一切都应该被认为是昂贵的,但不如熵。
  3. 我的想法是将二进制数字用于一半,quater ... maxValue空格,直到它减少到一个数字。像

    这样的东西

    我使用maxValue = 333(十进制)作为示例并假设函数getBit(),随机返回0或1

    offset:=0
    space:=maxValue
    
    while (space>0)
    
      //Right-shift the value, keeping the rightmost bit this should be 
      //efficient on x86 and x64, if coded in real code, not pseudocode
      remains:=space & 1
      part:=floor(space/2)
      space:=part
    
      //In the 333 example, part is now 166, but 2*166=332 If we were to simply chose one
      //half of the space, we would be heavily biased towards the upper half, so in case
      //we have a remains, we consume a bit of entropy to decide which half is bigger
    
      if (remains)
        if(getBit())
          part++;
    
      //Now we decide which half to chose, consuming a bit of entropy
      if (getBit())
        offset+=part;
    
      //Exit condition: The remeinind number space=0 is guaranteed to be met
      //In the 333 example, offset will be 0, 166 or 167, remaining space will be 166
    }
    
    randomResult:=offset
    

    getBit()可以来自你的熵源,如果它是基于位的,或者是在第一次调用时一次消耗n位熵(显然n是你的熵源的最佳值),并且直到空无一人。

答案 2 :(得分:0)

我目前的解决方案。我的口味有点难看。每个生成的数字也有两个分区,这可能会对性能产生负面影响(我还没有对这部分进行过分析)。

uint UniformUInt(uint maxResult)
{
    uint rand;
    uint count = maxResult + 1;

    if (maxResult < 0x100)
    {
        uint usefulCount = (0x100 / count) * count;
        do
        {
            rand = Byte();
        } while (rand >= usefulCount);
        return rand % count;
    }
    else if (maxResult < 0x10000)
    {
        uint usefulCount = (0x10000 / count) * count;
        do
        {
            rand = UInt16();
        } while (rand >= usefulCount);
        return rand % count;
    }
    else if (maxResult != uint.MaxValue)
    {
        uint usefulCount = (uint.MaxValue / count) * count;//reduces upper bound by 1, to avoid long division
        do
        {
            rand = UInt32();
        } while (rand >= usefulCount);
        return rand % count;
    }
    else
    {
        return UInt32();
    }
}

ulong UniformUInt(ulong maxResult)
{
    if (maxResult < 0x100000000)
        return InternalUniformUInt((uint)maxResult);
    else if (maxResult < ulong.MaxValue)
    {
        ulong rand;
        ulong count = maxResult + 1;
        ulong usefulCount = (ulong.MaxValue / count) * count;//reduces upper bound by 1, since ulong can't represent any more
        do
        {
            rand = UInt64();
        } while (rand >= usefulCount);
        return rand % count;
    }
    else
        return UInt64();
}