在C中生成INTEGERS的均匀分布

时间:2012-07-25 01:54:21

标签: c algorithm statistics integer uniform

我编写了一个C函数,我认为从均匀分布中选择整数,其范围为[rangeLow,rangeHigh]。这不是功课 - 我只是在一些嵌入式系统中使用它来修补我正在做的事情。

在我的测试用例中,此代码似乎产生了适当的分布。但是,我并不完全相信实施是正确的。 如果我在这里做错了什么,有人可以做一次健全检查并让我知道吗?

//uniform_distribution returns an INTEGER in [rangeLow, rangeHigh], inclusive.
int uniform_distribution(int rangeLow, int rangeHigh)
{
    int myRand = (int)rand(); 
    int range = rangeHigh - rangeLow + 1; //+1 makes it [rangeLow, rangeHigh], inclusive.
    int myRand_scaled = (myRand % range) + rangeLow;
    return myRand_scaled;
}
//note: make sure rand() was already initialized using srand()

P.S。我搜索了这样的其他问题。但是,很难过滤掉讨论随机整数而不是随机浮点数的小问题子集。

4 个答案:

答案 0 :(得分:12)

假设rand()在[0..RAND_MAX]范围内生成均匀分布的值I, 并且您希望在[L,H]范围内生成均匀分布的值O.

假设I in是[0..32767]范围而O在[0..2]范围内。

根据您建议的方法,O = I%3。请注意,在给定范围内,有10923个数字,其中I%3 = 0,10923数字,其中I%3 = 1,但仅有10922个数字,其中I%3 = 2。因此,您的方法不会将I中的值统一映射到O.

作为另一个例子,假设O在[0..32766]范围内。

根据您建议的方法,O = I%32767。现在,对于I = 0和I = 32767,你将得到O = 0。因此,0是任何其他值的两倍 - 您的方法再次不均匀。


生成统一映射的建议方法如下:

  1. 计算在[L,H]范围内存储随机值所需的位数:

    unsigned int nRange =(unsigned int)H - (unsigned int)L + 1;
    unsigned int nRangeBits =(unsigned int)ceil(log((double(nRange)/ log(2。));

  2. 生成nRangeBits随机位

    这可以通过右移rand()

  3. 的结果来轻松实现
  4. 确保生成的数字不大于H-L。 如果是 - 重复步骤2.

  5. 现在,您只需添加L。

  6. 即可将生成的数字映射到O.

答案 1 :(得分:6)

在某些实现中,rand()在其低阶位上没有提供良好的随机性,因此模数运算符不会提供非常随机的结果。如果你发现是这种情况,你可以试试这个:

int uniform_distribution(int rangeLow, int rangeHigh) {
    double myRand = rand()/(1.0 + RAND_MAX); 
    int range = rangeHigh - rangeLow + 1;
    int myRand_scaled = (myRand * range) + rangeLow;
    return myRand_scaled;
}

使用rand()这种方式会产生Lior所指出的偏见。但是,如果您能找到统一数字生成器来计算myRand,那么该技术就可以了。一个可能的候选人是drand48()。这将极大地减少对难以检测的东西的偏见量。

但是,如果你需要加密安全的东西,你应该使用Lior的答案中概述的算法,假设你的rand()本身是加密安全的(默认值可能不是,所以你需要找到一个)。以下是Lior所描述的简化实现。我们假设范围落在RAND_MAX范围内,并计算合适的倍数,而不是计算位数。最糟糕的情况是,该算法最终会针对该范围内的数字平均每次请求两次调用随机数生成器。

int uniform_distribution_secure(int rangeLow, int rangeHigh) {
    int range = rangeHigh - rangeLow + 1;
    int secureMax = RAND_MAX - RAND_MAX % range;
    int x;
    do x = secure_rand(); while (x >= secureMax);
    return rangeLow + x % range;
}

答案 2 :(得分:3)

我认为rand()不是很好。这取决于您需要的“随机”数据有多好。

我想你可以写一个测试,然后计算卡方值,看看你的制服发生器有多好:

http://en.wikipedia.org/wiki/Pearson%27s_chi-squared_test

根据您的使用情况(不要将其用于在线扑克洗牌机),您可以考虑使用LFSR

http://en.wikipedia.org/wiki/Linear_feedback_shift_register

如果您只想要一些伪随机输出,它可能会更快。另外,据说他们可以统一,虽然我没有足够的数学来支持这种说法。

答案 3 :(得分:1)

纠正分配错误的版本(由Lior注明), 涉及rand()和。返回的高位 只使用整数数学(如果需要):

int uniform_distribution(int rangeLow, int rangeHigh)
{
    int range = rangeHigh - rangeLow + 1; //+1 makes it [rangeLow, rangeHigh], inclusive.
    int copies=RAND_MAX/range; // we can fit n-copies of [0...range-1] into RAND_MAX
    // Use rejection sampling to avoid distribution errors
    int limit=range*copies;    
    int myRand=-1;
    while( myRand<0 || myRand>=limit){
        myRand=rand();   
    }
    return myRand/copies+rangeLow;    // note that this involves the high-bits
}

//注意:确保已使用srand()

初始化rand()

如果range远小于RAND_MAX,这应该可以正常工作,否则 你将回到rand()就其低位而言不是一个好的随机数发生器的问题。