生成具有可变比例“1”位的随机二进制数

时间:2010-01-16 01:57:06

标签: java optimization random bit-manipulation

我需要一个函数来生成随机整数。 (现在假设Java long类型,但稍后会扩展到BigIntegerBitSet。)

棘手的部分是有一个参数P,它指定结果中任何位的(独立)概率为1.

如果P = 0.5,那么我们可以使用标准随机数发生器。 P的一些其他值也易于实现。这是一个不完整的例子:

Random random = new Random();

// ...

long nextLong(float p) {
    if      (p == 0.0f)   return 0L;
    else if (p == 1.0f)   return -1L;
    else if (p == 0.5f)   return random.nextLong();
    else if (p == 0.25f)  return nextLong(0.5f) & nextLong(0.5f);
    else if (p == 0.75f)  return nextLong(0.5f) | nextLong(0.5f);
    else if (p == 0.375f) return nextLong(0.5f) & nextLong(0.75f); // etc
    else {
      // What goes here??
      String message = String.format("P=%f not implemented yet!", p);
      throw new IllegalArgumentException(message);
    }
}

有没有办法可以将P的任何值在0.0和1.0之间进行推广?

7 个答案:

答案 0 :(得分:4)

首先,你已经在代码中使用了一些丑陋的数学。

定义x和y分别是X = p(x = 1),Y = p(y = 1)的概率为1的位。 那我们就有了

 p( x & y = 1) = X Y
 p( x | y = 1) = 1 - (1-X) (1-Y)
 p( x ^ y = 1) = X (1 - Y) + Y (1 - X)

现在,如果我们让Y = 1/2,我们得到

P( x & y ) = X/2
P( x | y ) = (X+1)/2

现在将RHS设置为我们想要的概率,我们有两种情况可以解决X

X = 2 p        // if we use &
X = 2 p - 1    // if we use |

接下来我们假设我们可以再次使用它来获得另一个变量Z的X ... 然后我们继续迭代,直到我们完成“足够”。

有点不清楚,但考虑到p = 0.375

0.375 * 2 = 0.75  < 1.0 so our first operation is &
0.75 * 2 = 1.5 > 1.0 so our second operation is |
0.5 is something we know so we stop.

因此我们可以通过X1&amp;获得p = 0.375的变量。 (X2 | X3)

问题是对于大多数变量,这不会终止。 e.g。

0.333 *2 = 0.666 < 1.0 so our first operation is &
0.666 *2 = 1.333 > 1.0 so our second operation is |
0.333 *2 = 0.666 < 1.0 so our third operation is &
etc...

所以p = 0.333可以通过

生成
X1 & ( X2 | (X3 & (X4 | ( ... ) ) ) )

现在我怀疑在系列中获取足够的术语会给你足够的准确性,这可以写成一个递归函数。然而,也许有更好的方式......我认为操作的顺序与p的二进制表示有关,我只是不确定如何......并且没有时间更深入地考虑它。

无论如何,这是一些未经测试的C ++代码。您应该能够轻松地进行java化。

uint bitsWithProbability( float p )
{
   return bitsWithProbabilityHelper( p, 0.001, 0, 10 );
}

uint bitsWithProbabilityHelper( float p, float tol, int cur_depth, int max_depth )
{
   uint X = randbits();
   if( cur_depth >= max_depth) return X;
   if( p<0.5-tol)
   {
     return X & bitsWithProbabilityHelper( 2*p, 0.001, cur_depth+1, max_depth );
   }
   if(p>0.5+tol)
   {
     return X | bitsWithProbabilityHelper( 2*p-1, 0.001, cur_depth+1, max_depth );
   }
   return X;
}

答案 1 :(得分:2)

通过数字分配比例位数。 伪代码:

long generateNumber( double probability ){
  int bitCount = 64 * probability;
  byte[] data = new byte[64]; // 0-filled

  long indexes = getRandomLong();

  for 0 to bitCount-1 {
    do { 
      // distribute this bit to some postition with 0.
      int index = indexes & 64;
      indexes >> 6;
      if( indexes == 0 ) indexes = getRandomLong();
    } while ( data[index] == 0 );
    data[index] = 1;
  }

  return bytesToLong( data );
}    

我希望你明白我的意思。也许byte[]可以替换为long和位操作以使其更快。

答案 2 :(得分:1)

使用随机生成器生成0到1之间的统一浮点数r。如果r> p,则将该位设置为0,否则将其设置为1

答案 3 :(得分:1)

如果你想要在概率为P的情况下应用一些分布,你得到1并且概率为1-P你在任何特定位得到0你最好的选择就是单独生成每个位,概率P为a 1(听起来像一个递归的定义,我知道)。

这是一个解决方案,我将在下面介绍:

public class MyRandomBitGenerator
{

    Random pgen = new Random();

    // assumed p is well conditioned (0 < p < 1)
    public boolean nextBitIsOne(double p){
        return pgen.nextDouble() < p ? true : false;
    }

    // assumed p is well conditioned (0 < p < 1)
    public long nextLong(double p){
        long nxt = 0;
        for(int i = 0; i < 64; i++){
           if(nextBitIsOne(p)){
               nxt += 1 << i;
           }
        }
        return nxt;
    }

}

基本上,我们首先确定如何使用概率P生成值1:pgen.nextDouble()通过询问是否小于p我们正在采样,生成一个介于0和1之间且具有均匀概率的数字这个分布使得我们期望看到p 1s,因为我们无限地调用这个函数。

答案 4 :(得分:1)

以下是我最终解决的问题。

  1. 在二项分布之后生成介于0..16之间的整数N.这给出了16位部分结果中的“1”位数。
  2. 在包含所需数量“1”位的16位整数的查找表中随机生成索引。
  3. 重复4次以获得4个16位整数。
  4. 将这四个16位整数拼接在一起得到一个64位整数。
  5. 部分灵感来自OndraŽižka的答案。

    好处是它减少了每64位输出调用Random.nextLong()到8次调用的次数。 为了比较,每个位的滚动需要64次调用。按位AND / OR使用2到32个调用,具体取决于P

    的值

    当然,计算二项式概率同样昂贵,所以那些进入另一个查找表。

    这是很多代码,但它在性能方面取得了成效。


    更新 - 将其与按位AND / OR解决方案合并。它现在使用该方法,如果它猜测它将更有效(就调用Random.next()而言。)

答案 5 :(得分:1)

以下是Michael Anderson's answer

的另一种变体

为了避免递归,我们从右到左迭代地处理P的位,而不是从左到右递归地处理。这在浮点表示中会很棘手,所以我们从二进制表示中提取指数/尾数字段。

class BitsWithProbabilityHelper {
    public BitsWithProbabilityHelper(float prob, Random rnd) {
        if (Float.isNaN(prob)) throw new IllegalArgumentException();

        this.rnd = rnd;

        if (prob <= 0f) {
            zero = true;
            return;
        }

        // Decode IEEE float
        int probBits = Float.floatToIntBits(prob);
        mantissa = probBits & 0x7FFFFF;
        exponent = probBits >>> 23;

        // Restore the implicit leading 1 (except for denormals)
        if (exponent > 0) mantissa |= 0x800000;
        exponent -= 150;

        // Force mantissa to be odd
        int ntz = Integer.numberOfTrailingZeros(mantissa);
        mantissa >>= ntz;
        exponent += ntz;
    }

    /** Determine how many random words we need from the system RNG to
     *  generate one output word with probability P.
     **/
    public int iterationCount() {
        return - exponent;
    }

    /** Generate a random number with the desired probability */
    public long nextLong() {
        if (zero) return 0L;

        long acc = -1L;
        int shiftReg = mantissa - 1;
        for (int bit = exponent; bit < 0; ++ bit) {
            if ((shiftReg & 1) == 0) {
                acc &= rnd.nextLong();
            } else {
                acc |= rnd.nextLong();
            }
            shiftReg >>= 1;
        }
        return acc;
    }

    /** Value of <code>prob</code>, represented as m * 2**e where m is always odd. */
    private int exponent;  
    private int mantissa;

    /** Random data source */
    private final Random rnd;

    /** Zero flag (special case) */
    private boolean zero;
}

答案 6 :(得分:0)

假设位数组的大小为L.如果L = 1,则第1位为1的概率为P,0为1-P。对于L = 2,得到00的概率是(1-P) 2 ,01或10是P(1-P),11是P 2 。扩展这个逻辑,我们可以先通过比较随机数和P来确定第一位,然后缩放随机数,这样我们就可以再次获得0到1之间的任何值。示例javascript代码:

function getRandomBitArray(maxBits,probabilityOf1) {
    var randomSeed = Math.random();
    bitArray = new Array();
    for(var currentBit=0;currentBit<maxBits;currentBit++){
        if(randomSeed<probabilityOf1){
            //fill 0 at current bit
            bitArray.push(0);
            //scale the sample space of the random no from [0,1)
            //to [0.probabilityOf1)
            randomSeed=randomSeed/probabilityOf1;
        }
        else{
            //fill 1 at current bit
            bitArray.push(1);
            //scale the sample space to [probabilityOf1,1)
            randomSeed = (randomSeed-probabilityOf1)/(1-probabilityOf1);
        }
    }
}

修改 该代码确实生成完全随机的位。我会尝试更好地解释算法。

每个位串都有一定的发生概率。假设一个字符串有发生概率 p ;如果我们的随机数下降是长度为p的某个区间,我们想要选择该字符串。区间的起点必须固定,但其值不会有太大差异。假设我们正确选择了高达k位。然后,对于下一位,我们将对应于此k长度位串的间隔分成两部分大小,比例为 P:1 - P (此处< em> P 是获得1)的概率。我们说如果随机数在第一部分中,则下一位将为1,如果在第二部分中则为0。这确保了长度为k + 1的字符串的概率也保持正确。

Java代码:

public ArrayList<Boolean> getRandomBitArray(int maxBits, double probabilityOf1) {
    double randomSeed = Math.random();
    ArrayList<Boolean> bitArray = new ArrayList<Boolean>();
    for(int currentBit=0;currentBit<maxBits;currentBit++){
        if(randomSeed<probabilityOf1){
            //fill 0 at current bit
            bitArray.add(false);
            //scale the sample space of the random no from [0,1)
            //to [0.probabilityOf1)
            randomSeed=randomSeed/probabilityOf1;
        }
        else{
            //fill 1 at current bit
            bitArray.add(true);
            //scale the sample space to [probabilityOf1,1)
            randomSeed = (randomSeed-probabilityOf1)/(1-probabilityOf1);
        }
    }
    return  bitArray;
}