【问题标题】:Will this give me proper random numbers based on these probabilities? C++这会根据这些概率给我适当的随机数吗? C++
【发布时间】:2012-01-09 03:29:16
【问题描述】:

代码:

int random = (rand() % 7 + 1)
if (random == 1) { } // num 1
else if (random == 2) { } // num 2
else if (random == 3 || random == 4) { } // num 3
else if (random == 5 || random == 6) { } // num 4
else if (random == 7) { } // num 5

基本上,我希望这些数字中的每一个具有这些概率: 1:1/7 2:1/7 3:2/7 4:2/7 5:1/7

这段代码会给我正确的结果吗? IE。如果这是无限次运行,我会得到正确的频率吗?有没有更短的方法来做到这一点?

【问题讨论】:

  • 使用开关代替,七个数字的大小写。
  • “正确”是指“不可预测的、独特的随机噪声”还是只是“在统计上正常”?
  • 只是统计上没问题是我想要得到的 :)
  • 如果RAND_MAX32768,您将获得(大致)4682/32768, 4681/32768, 9362/32768, 9362/32768, 4681/32768 的分布。这在您预期频率的 0.03% 范围内。这在统计上可以吗? (注意前两个是不同的)
  • @MooingDuck,定义“通常”。微软为 RAND_MAX 使用 0x7fff,他们的编译器被广泛使用。

标签: c++ random probability


【解决方案1】:
int toohigh = RAND_MAX - RAND_MAX%7;
int random;
do { 
    random = rand();
while (random >= toohigh); //should happen ~0.03% of the time
static const int results[7] = {1, 2, 3, 3, 4, 4, 5};
random = results[random%7];

这应该会给出具有rand 可以处理的分布的数字,并且没有大的if 开关。

请注意,这确实存在理论上可能的无限循环,但它在偶数循环中停留的统计几率微乎其微。它留在循环中的几率两次 非常接近赢得加州超级乐透大奖的几率。即使这个星球上的每个人都有五个随机数,它也可能不会在循环中停留三遍。 (假设是一个完美的 RNG。)

【讨论】:

    【解决方案2】:

    假设 rand() 是好的,那么您的代码将只对较低的 X 数字产生很小的偏差,其中 X 是 RAND_MAX % 7。由于质量问题,您更有可能无法获得所需的赔率rand()的执行。如果您发现是这种情况,那么您将需要使用替代的随机数生成器。

    C++11 引入了标头 <random>,其中包括几个高质量的 RNG。这是一个例子:

    #include <random>
    #include <functional>
    
    auto rand = std::bind(std::uniform_int_distribution<int>(1,7),std::mt19937());
    

    鉴于此,当您致电rand() 时,您将得到一个从 1 到 7 的数字,每个数字都具有相同的概率。 (如果针对不同的质量和速度特性,您可以选择不同的引擎。)然后您可以使用它来实现您的示例当前与std::rand() 一起使用的 if-else 条件。但是&lt;random&gt; 允许您使用其中一种非均匀分布做得更好。在这种情况下,您想要的是discrete_distribution。此分布允许您明确说明从 0 到 n 的每个值的权重。

    // the random number generator
    auto _rand = std::bind(std::discrete_distribution<int>{1./7.,1./7.,2./7.,2./7.,1./7.},std::mt19937());
    // convert results of RNG from the range [0-4] to [1-5]
    auto rand = [&_rand]() { return _rand() +1; };
    

    【讨论】:

    【解决方案3】:

    不,由于 rand() 的工作方式,它实际上略有偏差。特别是,rand 返回 [0,RAND_MAX] 范围内的值。假设地,假设 RAND_MAX 为 10。然后 rand() 将给出 0…10,它们将被映射(按模数)到:

    0  → 0
    1  → 1
    2  → 2
    3  → 3
    4  → 4
    5  → 5
    6  → 6
    7  → 0
    8  → 1
    9  → 2
    10 → 3
    

    请注意 0-3 比 4-6 更常见;这是您的随机数生成中的偏差。 (您也添加了 1,但这只是将其转移)。

    RAND_MAX 当然不是 10,但也可能不是 7(减 1)的倍数。很可能是二的幂。所以你会有一些偏见。

    我建议使用Boost Random Number Library,它可以为您提供一个随机数生成器,生成 1-7 且无偏差。另请查看bames53's answer using C++11,如果您的代码只需要针对 C++11 平台,这是正确的方法。

    【讨论】:

    • 由此产生的偏差可能非常小。假设rand() 很好并且最大值是更现实的 2^32 - 1,则对较低四个值中的每一个的偏差将约为 7e-8%。 rand() 的实施质量将产生更大的影响。
    • @bames53:嗯,有时 RAND_MAX 只是 2¹⁵-1 或经常是 2³¹-1。 2³²-1 对于 32 位整数是不可能的(即使在 64 位机器上也是正常的,其中 long 通常是 64 位类型)。但是升压发生器 (a) 易于使用; (b) 没有这种偏见; (c) 至少应该一样快; (d) 您不必担心 rand() 的错误 C 库实现。
    • derobert,偏差仍然很小,RAND_MAX 为 2^31 - 1,即使是 2^15 - 1,也只有 0.003%。如果 C++11 不可用,Boost 是一个合理的选择(请参阅我对 C++11 示例的回答,这也可能转化为 boost)。
    • @bames53:您的 C++11 示例确实不错。我对他们投了赞成票。但是由于 Boost 和 C++11 都提供了无偏见(并且可能更快)的生成器,因此没有任何理由接受偏见,即使它很小。不妨养成正确做事的习惯。
    【解决方案4】:

    只是另一种方式:

    float probs[5] = {1/7.0f, 1/7.0f, 2/7.0f, 2/7.0f, 1/7.0f};
    float sum = 0;
    for (int i = 0; i < 5; i++)
      sum += probs[i]; /* edit */
    int rand_M() {
      float f = (rand()*sum)/RAND_MAX; /* edit */
      for (int i = 0; i < 5; i++) {
        if (f <= probs[i]) return i;
        f -= probs[i];
      }
      return 4;
    }
    

    【讨论】:

    • 偏向于最后一个元素不是吗?由于求和时概率的舍入误差?
    • 我已经接近了,但仍然没有:ideone.com/PG68c。漂浮在周围。添加它们会使它复杂化。它给出倒数第二个数字稍微太多可能性〜.00000003,而最后一个太少〜-.00000004。
    【解决方案5】:

    rand 返回-随机整数:

    请注意,虽然此模运算 不会生成真正的 跨度内均匀分布的随机数(因为在大多数情况下 较低的数字更有可能),但通常是一个好的 短跨度的近似值。


    现在,关于不太长的方式,您可以使用 switch-case 构造,或一系列 conditional operators ?:(这将使您的代码简短且不可读:)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-15
      • 1970-01-01
      • 2013-08-27
      • 2012-02-22
      相关资源
      最近更新 更多