【问题标题】:Generating a uniform distribution of INTEGERS in C在 C 中生成 INTEGERS 的均匀分布
【发布时间】:2012-07-25 01:54:21
【问题描述】:

我编写了一个 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()

附:我搜索了其他类似的问题。但是,很难过滤掉讨论随机整数而不是随机浮点数的一小部分问题。

【问题讨论】:

  • 为了获得不错的随机性,您可能必须使用特定于平台的东西,或者至少使用标准 C 之外的东西,例如POSIX 或 BSD 规范函数

标签: c algorithm statistics integer uniform


【解决方案1】:

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

假设 I 在 [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] 范围内存储随机值所需的位数:

    无符号整数 nRange = (无符号整数)H - (无符号整数)L + 1;
    unsigned int nRangeBits= (unsigned int)ceil(log((double(nRange) / log(2.));

  2. 生成 nRangeBits 随机位

    这可以通过右移 rand() 的结果来轻松实现

  3. 确保生成的数字不大于 H-L。 如果是 - 重复第 2 步。

  4. 现在您只需添加 L 即可将生成的数字映射到 O。

【讨论】:

  • 我已经引用了这个很好的答案here。小的候选改进 ceil(log((double(nRange) / log(2.)) --> ceil(log2((double)nRange)) 或其他一些仅整数计算。
【解决方案2】:

在某些实现中,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;
}

【讨论】:

  • 应该是“return rangeLow + x % range;”。
【解决方案3】:

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

我想你可以写一个测试然后计算卡方值来看看你的统一生成器有多好:

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

根据您的用途(不要将它用于您的在线扑克洗牌器),您可能会考虑使用 LFSR

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

如果您只是想要一些伪随机输出,它可能会更快。此外,据说它们可以是统一的,尽管我对数学的研究不足以支持这一说法。

【讨论】:

    【解决方案4】:

    更正分发错误的版本(由 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
    }
    

    //注意:确保 rand() 已经使用 srand() 进行了初始化

    如果rangeRAND_MAX 小得多,这应该可以正常工作,否则 你会回到rand() 就其低位而言不是一个好的随机数生成器的问题。

    【讨论】:

    • 你的意思是 myRand = 限制,不是吗?为什么不使用 do while 呢?
    • @Marc 我系统地使用半开区间来处理这样的事情;参考文献cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html 并避免将 do-while 作为我“风格”的一部分。
    • 好的,戴夫,但是 myRand 永远不会同时是 = 限制。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-14
    • 2018-03-09
    • 1970-01-01
    • 1970-01-01
    • 2015-08-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多