Random 如何从随机比特流生成[0,n]范围内的随机整数而不浪费比特?

Random 如何从随机比特流生成[0,n]范围内的随机整数而不浪费比特?,random,Random,我有一个(均匀的)随机比特流,我想从中均匀地生成[0,n]范围内的随机整数,而不浪费比特。(我考虑的是浪费的位超过了下限(log_2(n))+1,假设总是可以使用不超过这个值。)例如,如果n=5,那么我正在寻找的算法应该使用不超过3位。如何做到这一点?看起来您可以一次获取x=ceil(log_2(n))位,并将其用作随机数。您将遇到的问题是,如果您收到的数字大于您的限制(例如5),那么您将希望执行一些魔术,使其小于5,但保持一致。在这种情况下,似乎合乎逻辑的是,您只需再获取一个x位,但既然您已经

我有一个(均匀的)随机比特流,我想从中均匀地生成[0,n]范围内的随机整数,而不浪费比特。(我考虑的是浪费的位超过了下限(log_2(n))+1,假设总是可以使用不超过这个值。)例如,如果n=5,那么我正在寻找的算法应该使用不超过3位。如何做到这一点?

看起来您可以一次获取x=ceil(log_2(n))位,并将其用作随机数。您将遇到的问题是,如果您收到的数字大于您的限制(例如5),那么您将希望执行一些魔术,使其小于5,但保持一致。在这种情况下,似乎合乎逻辑的是,您只需再获取一个x位,但既然您已经指定我们不能浪费位,那么我们就必须更具创造性。我会建议你右转或左转,但这并不总是能让你摆脱这种状况。(当需要n=5时,考虑一个111的字符串)。我们可以做最多x个旋转,看看其中一个旋转是否能使我们进入正确的范围,或者我们可以翻转所有的位并加上1(两个的补码)。我相信这将使它统一起来

例如,如果您有以下字符串(最右边的位是您收到的第一个):

101001111010010101

使用n=5,然后使用ceil(log2(n))=3,所以一次使用三位,以下是结果(在每个时间步):


首先找出要生成的可能值的数量。如果整数的范围为0..5,则为6个值。它们可以用ceil(log(6)/log(2))位表示

<代码> //在C++中 std::位集<3>位; //填充位集 //解释为数字 长值=位到_ulong(); 然后找到从n位到最终表示格式的转换:它需要从范围[0..2N]缩放到范围[from,to]:

double out_from=-1, out_to=5;
double in_from=0, in_to = std::bitset<3>().flip().to_ulong();

double factor   = (out_to-out_from)/(in_to-in_from)
double constant = out_from - in_from;

double rescaled = in_value * scale + constant;
long out = floor( rescaled );
double out\u from=-1,out\u to=5;
双进位from=0,进位to=std::bitset().flip().to_ulong();
双因子=(输出到输出自)/(输入到输入自)
双常数=out\u from-in\u from;
双重定标=单位值*标度+常数;
长出=地板(重新缩放);

这相当于在两组不同(有限)基数之间找到一个双向函数。这是不可能的。

尽管您的问题描述指定了生成的每个随机数的固定位数,但您的标题没有指定。所以我要在这里加上,平均来说,你可以生成一个随机数,用你声明的位数加上半位数。下面的算法对不可被2整除的n值采用可变位数,但它将消耗的平均位数为下限(log_2(n))+1.5

生成一个范围内的整数的函数的标准实现在一个大的随机数上使用%(模)。这会浪费位,并且不会产生数学上精确的随机分布,除非对一些大随机数的值重新运行。以下算法产生真实的随机分布,不会浪费位。(或者更确切地说,我没有看到一个明显的方法来减少它所消耗的比特数。也许可以从“数字太大”事件中恢复一些熵。)

#在不浪费位的情况下,生成一个从0到n(含0到n)的数字。
函数随机整数(n)
如果n=0
r=r+(2^x)*NextRandomBit()
如果r>n
#所选数字太大,请重新开始。
x=i
r=0
其他的
#还在射程内。计算下一位。
x=x-1
返回r

上面的算法是为了清晰而不是速度而编写的。如果重写为一次处理多个位,速度会非常快。

让我谈谈随机整数生成算法,这些算法在平均使用的随机位数量方面是“最优的”。在这篇文章的其余部分,我们将假设我们有一个“真实”的随机生成器,它可以生成无偏和独立的随机位

1976年,D.E.Knuth和A.C.Yao证明,任何只使用随机位产生给定概率的随机整数的算法都可以表示为二叉树,其中随机位表示遍历树的方式,每个叶(端点)对应一个结果。Knuth和Yao指出,任何用于统一生成
[0,n)
中整数的优化二叉树算法都需要至少
log2(n)
,并且平均最多
log2(n)+2
位(因此,即使是优化算法也有可能“浪费”位)。有关优化算法的示例,请参见下文

然而,一般来说,任何无偏的最优整数生成器都会在最坏的情况下永远运行,正如Knuth和Yao所示。回到二叉树,n个结果标签中的每一个都会留在二叉树中,因此[0,n]中的每个整数都可能以1/n的概率出现。但是如果1/n有一个非终止的二叉展开(如果n不是2的幂,则会出现这种情况),此二叉树必然-

  • 具有“无限”深度,或
  • 包括树末端的“拒绝”叶子
在任何一种情况下,算法都将在最坏的情况下永远运行,即使它平均使用很少的随机位。(另一方面,当n是2的幂时,最优二叉树将没有拒绝节点,并且在返回结果之前需要正好n位,因此不会“浪费”任何位。)快速掷骰子是一个使用“拒绝”事件确保其无偏见的算法示例;请参见下面代码中的注释

因此,一般来说,一个随机整数生成器可以是无偏的,也可以是恒定时间的(甚至两者都不是),但是没有
// in C++
std::bitset< 3 > bits;
// fill the bitset

// interpret as a number
long value = bits.to_ulong();
double out_from=-1, out_to=5;
double in_from=0, in_to = std::bitset<3>().flip().to_ulong();

double factor   = (out_to-out_from)/(in_to-in_from)
double constant = out_from - in_from;

double rescaled = in_value * scale + constant;
long out = floor( rescaled );
# Generate a number from 0 to n inclusive without wasting bits.
function RandomInteger(n)
    if n <= 0
        error
    else
        i = Floor(Log2(n))
        x = i
        r = 0
        while x >= 0
            r = r + (2 ^ x) * NextRandomBit()
            if r > n 
                # Selected number too large so begin again.
                x = i 
                r = 0
            else
                # Still in range. Calculate the next bit.
                x = x - 1
        return r
function randomInt(minInclusive, maxExclusive) {
 var maxInclusive = (maxExclusive - minInclusive) - 1
 var x = 1
 var y = 0
 while(true) {
    x = x * 2
    var randomBit = nextBit()
    y = y * 2 + randomBit
    if(x > maxInclusive) {
      if (y <= maxInclusive) { return y + minInclusive }
      // Rejection
      x = x - maxInclusive - 1
      y = y - maxInclusive - 1
    }
 }
}