【问题标题】:Generating random boolean生成随机布尔值
【发布时间】:2017-09-05 20:41:43
【问题描述】:

我目前正在用 C++ 实现 Eller's Algorithm,一个小细节让我对迷宫的随机性感到困扰。

直到现在我使用下面的代码来生成一个随机的bool

bool randomBool()
{
    return 0 + (rand() % (1 - 0 + 1)) == 1;
}

// In main.cpp

time_t seconds;
time(&seconds);
srand((unsigned int) seconds);

但在调试时,我经常看到重复生成 truefalse,有时连续生成多达 30 次。

这个算法真的是随机的还是C++中有更好的方法?

【问题讨论】:

  • 有重复是正常的,否则序列将无限交替truefalse,本质上是零熵(随机性)。
  • 如何判断随机性?怎么说30个真、2个假、30个真后就不是随机的了?
  • 0 + (rand() % (1 - 0 + 1)) == 1 @#$% 是什么,它比rand() % 2 好多少?
  • 1. rand() 以在底部不随机而闻名(这是您正在测试的)。 2. 真的是随机的吗?绝对不是(但在计算机上很少有真正随机的)。但是有些系统具有更好的熵(例如 std::random)。 3. 条纹在真正随机的系统中并不少见;给定足够大的输入集,它们很可能会发生(并非不可能)。
  • @n.'pronouns'm。 LMFAO,这是我读完这些废话后的第一个想法!尤其是(1 - 0 + 1) 部分。如果他们想要“表示 2 的最奇怪的方式”,他们可能也写了(232783 - 232782 + 1):P

标签: c++ random boolean


【解决方案1】:

C++11 中的 STL 内置了优于 rand() 的随机数生成方法。您可以通过 0 或 1 的随机整数来模拟随机布尔值:

#include <iostream>
#include <random>

int main(int argc, char *argv[]) {
    auto gen = std::bind(std::uniform_int_distribution<>(0,1),std::default_random_engine());
    const unsigned int N = 100;
    unsigned int numTrue = 0;
    unsigned int numFalse = 0;
    for (int i = 0; i < 100; ++i) {
        bool b = gen();
        if (b) ++ numTrue;
        else ++numFalse;
    }
    std::cout << numTrue << " TRUE, " << numFalse << " FALSE" << std::endl;
}

您可以在标准 C++ 参考中找到有关此库的更多详细信息。例如,如果您想要的不是“真”和“假”值的 50/50 比率,则可以创建一个介于 0 和 1 之间的随机浮点数,并将小于某个阈值 z 的值称为真,否则为假。

我认为为什么你会看到长条纹

我还没有说明为什么您的代码会连续获得 30 个“真”或“假”值。尽管不应再使用rand(),并且您的代码中似乎有一些不必要的加减1和0,但不应该有这样的问题。但是,我现在意识到您问题中的文字模棱两可。如果您连续运行和退出程序 30 次,您应该会看到重复的值——即使使用我的代码也是如此。大多数随机数生成器实际上是伪随机数生成器。每次运行程序时,它们都会产生相同的随机数序列;这对于结果的一致性很重要。但是,当程序运行时(例如,将您的 randomBool() 放入循环中),您不应该看到这么长的条纹,因为它们不太可能出现。

不可能出现长条纹

我很惊讶地收到 cmets 不同意我的断言,即连续出现 30 个“真”或“假”随机布尔值是不可能的(当真或假的可能性相同时)。我意识到对概率的一个常见误解是“运气”试图使事情变得平衡,因此,如果掷硬币连续几次出现正面,那么宇宙将尝试纠正这一点并产生更多反面可能。由于这种误解,人们低估了所有正面和所有反面条纹的可能性,我认为 cmets 在这个答案和主要问题上的动机是纠正这个常见错误。

但是,有一个真正的原因是,长条纹(尤其是长达 30 条)越来越不可能。使用随机无偏抛硬币的语言,每次 IID(独立同分布)抛硬币只有 50% 的机会与前一次相同。因此,长条纹的概率随着条纹的长度呈指数下降。对于长度为 L 的连胜,所有正面连胜的概率为 1 in 2^L;任何一种类型的条纹的概率是 2 in 2^L 或 1 in 2^(L-1)。下面是一些代码来演示:

#include <iostream>
#include <random>
#include <map>

bool randomBool() {
    static auto gen = std::bind(std::uniform_int_distribution<>(0,1),std::default_random_engine());
    return gen();
}

int main(int argc, char *argv[]) {

    const unsigned int N = 1e8;
    std::map<unsigned int,unsigned int> histogram;
    bool current = randomBool();
    unsigned int currentLength = 1;
    for (int i = 0; i < N; ++i) {
        bool b = randomBool();
        if (b == current) {
            ++currentLength;
        } else {
            auto it = histogram.find(currentLength);
            if (it != histogram.end())
                it->second += 1;
            else
                histogram.insert(std::make_pair(currentLength,1));
            currentLength = 1;
        }
        current = b;
    }

    for (auto pair : histogram) 
        std::cout << "STREAK LENGTH " << pair.first << " OCCURS " << pair.second << " TIMES" << std::endl;
}

输出直方图为:

STREAK LENGTH 1 OCCURS 25011106 TIMES
STREAK LENGTH 2 OCCURS 12503578 TIMES
STREAK LENGTH 3 OCCURS 6249056 TIMES
STREAK LENGTH 4 OCCURS 3125508 TIMES
STREAK LENGTH 5 OCCURS 1560812 TIMES
STREAK LENGTH 6 OCCURS 781206 TIMES
STREAK LENGTH 7 OCCURS 390143 TIMES
STREAK LENGTH 8 OCCURS 194748 TIMES
STREAK LENGTH 9 OCCURS 97816 TIMES
STREAK LENGTH 10 OCCURS 48685 TIMES
STREAK LENGTH 11 OCCURS 24327 TIMES
STREAK LENGTH 12 OCCURS 12176 TIMES
STREAK LENGTH 13 OCCURS 6149 TIMES
STREAK LENGTH 14 OCCURS 3028 TIMES
STREAK LENGTH 15 OCCURS 1489 TIMES
STREAK LENGTH 16 OCCURS 811 TIMES
STREAK LENGTH 17 OCCURS 383 TIMES
STREAK LENGTH 18 OCCURS 193 TIMES
STREAK LENGTH 19 OCCURS 104 TIMES
STREAK LENGTH 20 OCCURS 43 TIMES
STREAK LENGTH 21 OCCURS 20 TIMES
STREAK LENGTH 22 OCCURS 14 TIMES
STREAK LENGTH 23 OCCURS 4 TIMES
STREAK LENGTH 24 OCCURS 3 TIMES

很难计算在翻转次数 N 中长度为 L 的条纹的预期数量,因为在许多重叠的长度为 L 的拉伸中可能存在这样的条纹。但是请注意,此直方图大致遵循指数分布,每个条目大约是前一个条目的一半。

最大连击数为 24 [注意:以前版本中的一个错误将其计为 23]。在任何 24 次抛掷的独立字符串中,出现这种长度的连胜的概率是 2^(24-1) 分之一,或大约 800 万分之一。由于在 1e8 次投掷中大约有 1e8/24 ~ 430 万次这样的独立延伸,我们预计会有少量这样的连续性,所以这似乎是正确的(我上面的警告是计算准确的期望是困难的)。与此同时,长度为 30 的连胜在任何独立的 30 次翻转中的概率为 5.37 亿分之一,甚至比长度为 24 的连胜的可能性要小得多。

【讨论】:

  • 你不应该看到这么长的条纹,因为它们不太可能出现。我不同意。当您只有 2 个可能的值时,我会发现结果很可能会出现条纹。即使在您的示例中运行次数很少,我也连续获得 5 次 (sample run)。
  • @NathanOliver 连续 5 个随机布尔值相等的可能是 2^5=32 或 1/16 中的 2 个案例(全部为真或全部为假) - 不太可能。但对于 30 例,可能是 2 ^ 30 中的 2 - 或 5.37 亿中的 1。
  • 实际上很可能出现长条。我期望掷硬币 1000 次,因为这组结果将包含一个长列表,匹配某个函数(全头、全尾或头/尾翻转)。
  • @jwimberley 概率不是这样累积的。每次您掷硬币时,您都有 50/50 的机会。之前的结果是什么并不重要。这使得连续上垒很容易。
  • 我鼓励你们两个都运行一些模拟并计算你发现长条纹的频率。连续 5 个左右的值的连续性是非常合理和可能的,但可能会随着连续性长度指数减少,这正是因为翻转是 IID 事件,每个新翻转只有 50% 的机会和之前的一样。
【解决方案2】:

如果rand() 是真正的伪随机,则它是真正的伪随机,尽管如果RAND_MAX 是偶数(即偶数比奇数多一个),分布可能会非常不均匀。但通常RAND_MAX 足够大,差异可以忽略不计。

【讨论】:

    【解决方案3】:
    bool randomBool() {
        return 0 + (rand() % (1 - 0 + 1)) == 1;
    }
    

    这可能是将rand() 的输出转换为布尔值的最糟糕的方法。在许多实现中,低位的随机性远低于高位。

    理想情况下,您会完全使用其他东西,但如果您必须使用rand(),请尝试:

    bool randomBool() {
       return rand() > (RAND_MAX / 2);
    }
    

    【讨论】:

    • 这会返回最长的连续 18 次,您的测试代码运行 100,000 次调用。
    【解决方案4】:

    伪随机数生成器的低位往往提供较少的随机性。对于内置的rand() 函数尤其如此,它通常实现为LCG。生成随机bool 的最佳方法是使用 MSB 位。这实际上是一个标准的Bernoulli distribution,概率为1/2

    #include <cmath>
    #include <cstdlib>
    
    inline bool random_bool()
    {
       static const int shift = static_cast<int>(std::log2(RAND_MAX));
       return (rand() >> shift) & 1;
    }
    

    【讨论】:

      【解决方案5】:

      这是一个 C++11 函数模板,它以指定的概率生成布尔结果(二项分布)(默认 0.5 表示均匀):

      #include <random>
      template <typename Prob = double>
      bool binomial_trial(const Prob p = 0.5) {
          static auto dev = std::random_device();
          static auto gen = std::mt19937{dev()};
          static auto dist = std::uniform_real_distribution<Prob>(0,1);
          return (dist(gen) < p);
      }
      

      【讨论】:

        猜你喜欢
        • 2016-08-13
        • 2023-03-28
        • 2014-10-06
        • 2013-10-12
        • 1970-01-01
        • 2017-10-30
        • 1970-01-01
        • 2012-01-15
        相关资源
        最近更新 更多