在C++中从正态分布生成随机数

7
作为一个完全的C++新手,我想从正态分布中生成一个随机数。
通过以下代码(源自此帖子),我可以实现这个目标:
#include <iostream>   
#include <boost/random.hpp>
#include <boost/random/normal_distribution.hpp>

using namespace std;

int main()
{
    boost::mt19937 rng(std::time(0)+getpid());
    boost::normal_distribution<> nd(0.0, 1.0);
    boost::variate_generator<boost::mt19937&,
                             boost::normal_distribution<> > rnorm(rng, nd);

    cout<< rnorm();
  return 0;
}

由于代码相当复杂(在我看来),我认为可能有更直接的解决方案:

#include <iostream>
#include <random>

using namespace std;

int main()
{   
    default_random_engine generator;
    normal_distribution<double> distribution(0.0,1.0);

    cout << distribution(generator);
    return 0;
}

虽然我可以生成随机数,但它总是相同的数字。 这引出了两个问题: (1) 为什么会这样,如何修复? (2) 是否有其他更简单的方法来生成随机数?

2
我不熟悉这些函数,但在C语言中,如果您没有给rand函数提供种子值,它将生成相同的值,因为该算法是确定性的而非随机的。因此,如果您使用相同的种子值(在缺少种子值的情况下可能为0),则会得到相同的结果。 - Eraklon
1
感谢您的迅速回复!我的确想要理解发生了什么,而不仅仅是从其他地方复制粘贴代码。例如,我不知道我需要为生成器提供一个种子。 - user213544
1
在第一个版本中,您添加了一个种子“std::time(0)+getpid())”,而在第二个版本中则没有。无论哪种方式,它都是伪随机的,因此对于相同的种子,您会得到相同的“随机数”。在第二个版本中,您还可以调用generator.seed(std::time(0)+getpid());以获得类似于第一个版本的效果。 - Nicolae Natea
2
@user213544,你有C++11编译器吗?如果有的话,请使用标准头文件<random>,而不是<boost/random.hpp> - Ted Lyngmo
显示剩余2条评论
2个回答

5

使用种子来初始化你的生成器。这里我使用了基于时间的种子。

#include <iostream>
#include <random>
#include <chrono>

using namespace std;

int main()
{
    unsigned seed = chrono::system_clock::now().time_since_epoch().count();
    default_random_engine generator(seed);
    normal_distribution<double> distribution(0.0, 1.0);

    cout << distribution(generator);
    return 0;
}

1
@TedLyngmo 边角案例 - MinGW 的 random_device 实现会产生相同的确定性值,这使得它在这种情况下无用。因此,上述解决方案更优,但我不知道它是否适用于其他地方。 - Fureeish
3
@Fureeish "在旧版本的MinGW中(bug 338,自GCC 9.2修复以来),std :: random_device是确定性的一个显着的实现,尽管存在替代实现,如mingw-std-random_device。最新的MinGW版本可以从具有MCF线程模型的GCC中下载。 " - 因此,与其改编代码以支持不符合规范的实现,不如尝试获取符合规范的实现,在MinGW中是可能实现的。 :-) - Ted Lyngmo
@TedLyngmo 如果每个人都能使用尖端的实现就好了:> - Fureeish
1
@user213544 default_random_engine 是实现定义的,因此很难给出通用的答案。如果我没记错的话,标准中的 mt19937 被锁定到某个版本的 Mersenne Twister。我不确定 boost 是否使用相同的实现。然而,标准中包含的 PRNG 不是最快的,也不是生成数据最好的。 - Ted Lyngmo
1
@user213544 在你还没有性能要求之前,不必担心性能问题。 - YSC
显示剩余4条评论

1
(1) 为什么会发生这种情况,我该如何修复它?
这是因为您默认构造了 PRNG(伪随机数生成器),并且没有进行种子设置。PRNG 生成确定性的数字序列。该序列通常非常长,然后重新开始。种子用于设置 PRNG 的内部状态 - 它的起始点。如果没有种子,则每次都会从相同的状态开始。
(2) 是否有其他更简单的方法来生成随机数?
不使用现代标准 C++(C++11 及更高版本)就没有了。
一些注意事项:
  • Using a time based seed based on a one-shot sample of the clock is considered bad since you risk seeding two PRNG:s with the same value.
  • You only need one PRNG (per thread that needs to generate random numbers) in you program - and seeding a PRNG is considered costly (in terms of speed). You could therefore make the generator global so it can be used everywhere in the program. The below is a thread safe version, initialized with what is supposed to be a True (but slow) RNG, generating numbers from an entropy pool, std::random_device.
    std::mt19937& prng() {           // extern declared in a header file
        static thread_local std::mt19937 gen(std::random_device{}());
        return gen;
    }
    
  • If your random_device lacks entropy or is buggy (older versions of MinGW had a buggy implementation) you can combine random_device output with a few time based numbers (sampled some time apart) to create a std::seed_seq that you use to initialize your PRNG. The below should work with both buggy and conformant implementations to create a seed that is hard to predict:

    #include <chrono>
    #include <thread>
    
    // Create a seed_seq with 2 time based numbers and 2 random_device numbers.
    // The two sleeps are done to ensure some diff in the clock counts.
    static std::seed_seq get_seed() {
        static constexpr auto min = std::chrono::steady_clock::duration::min();
        std::random_device rd;
        std::uint_least32_t si[4];
        for(size_t s = 0; s < std::size(si);) {
            si[s++] = rd();
            std::this_thread::sleep_for(min);
            si[s++] = static_cast<std::uint_least32_t>(
                std::chrono::steady_clock::now().time_since_epoch().count());
            std::this_thread::sleep_for(min);
        }
        return {si[0], si[1], si[2], si[3]};
    }
    
    std::mt19937& prng() {           // extern declared in a header file
        static thread_local std::seed_seq seed = get_seed();
        static thread_local std::mt19937 gen(seed);
        return gen;
    }
    

嗨,Ted。你能看一下我的正态分布问题吗?https://stackoverflow.com/questions/70900624 - WhoCares
@WhoCares,“好”的正态分布比我舒适地谈论要复杂一些。Lemire有一种形式的“均匀”分布公式,表现不错。我建议查看该领域专家的出版物以获得正确答案。它至少应该构成一个可靠的基础。 - Ted Lyngmo
对不起,Lemire 是谁? - WhoCares

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接