【问题标题】:Incorrectly seeding Mersenne Twister via constructor通过构造函数错误地播种 Mersenne Twister
【发布时间】:2017-11-23 15:02:14
【问题描述】:

我的构造函数有什么问题?每次我调用一个应该生成随机数的函数(大约每五秒一次)时,它都会生成相同的数字。每个调用都会实例化以下这些对象之一。我以为我是用m_rdoperator() 调用的输出随机播种m_gen

我可以将m_rd() 的结果传递给构造函数吗?签名会是什么? Shuffler(std::random device& rd)?但这对用户来说会更加困难。

编辑:

实际上,如果可能的话,我更喜欢不需要向构造函数传递任何内容的解决方案。

shuffler.h

#include <random>

class Shuffler
{
private:
    std::random_device m_rd;
    std::mt19937 m_gen;
public:

    //! The default constructor. 
    Shuffler();

};

shuffler.cpp

#include "shuffler.h"

Shuffler::Shuffler() : m_gen(m_rd())
{
}

【问题讨论】:

  • stackoverflow.com/questions/12458383/… 有什么有用的吗?
  • 一个 random_device 是 not 一个种子序列。
  • 您是否偶然使用了 MinGW?我相信std::random_device 没有该编译器的有用实现。见stackoverflow.com/q/18880654/10077
  • 如果random_device 是一个总是返回相同序列的伪随机生成器,那么每次都会得到相同的值,因为每次都创建一个新实例。如果您将m_rd 设为静态,则可以解决该问题。

标签: c++ random constructor std mersenne-twister


【解决方案1】:

std::random_device 通常适用于这类事情,但它可能并不适用于所有平台。虽然大多数平台的标准库根据一些底层操作系统随机功能(即 Linux 上的 /dev/urandom 或 Windows 上的 CryptGenRandom)来实现它,但 C++ 标准并不要求这样做。在某些平台上,可能根本无法使用高质量的随机生成器,而标准允许 std::random_device 是一个简单的静态种子 PRNG。如果是,每个std::random_device 对象都会生成相同的数字序列。

出于这些原因,您可能希望回到简单的时间播种。标准 提供std::chrono::high_resolution_clock:

class Shuffler
{
private:
    std::mt19937 m_gen;
public:
    Shuffler()
        : m_gen{static_cast<std::uint32_t>(
              std::chrono::high_resolution_clock::now().time_since_epoch().count()
          )}
    {}
};

std::chrono::high_resolution_clock 通常具有纳秒或数百纳秒的分辨率。这足够高,通过调用high_resolution_clock 播种的两个 PRNG 不太可能最终使用相同的种子。但是,这也不能保证。例如,std::chrono::high_resolution_clock 在 macOS 上只有微秒级的分辨率,这对于您的目的可能不够好,也可能不够好。

最后,这两种方法都不是完美的。您可能想使用std::seed_seq 将两者结合起来:

std::seed_seq make_seeds() {
    thread_local std::random_device rd;
    return {{
        static_cast<std::uint32_t>(std::chrono::high_resolution_clock::now().time_since_epoch().count()),
        rd()
    }};
}

// Cast away rvalue-ness because the standard random generators need
// an lvalue reference to their seed_seq for some strange reason
template <typename T>
T& identity(T&& t) { return t; }

class Shuffler
{
private:
    std::mt19937 m_gen;
public:
    Shuffler()
        : m_gen{identity(make_seeds())}
    {}
};

如您所见,这远非简单,而且还不完美。请参阅theseblogposts,了解有关播种和随机数生成器的更多信息,然后您会认为自己想要。

【讨论】:

    【解决方案2】:

    就像在这个example 中一样,你必须播种它,而random_device 似乎没有效果*

    // do this once somewhere
    unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
    
    
    class Shuffler
    {
    private:
        std::mt19937 m_gen;
    public:
        Shuffler()  : m_gen(seed) {}
    
    };
    

    *如here 所述,random_device 不是种子序列!

    【讨论】:

    • std::random_device 通常从操作系统中提取随机性(通常是 *nix 上的 /dev/urandom 或 Windows 上的 CryptGenRandom)。播种 PRNG 应该没问题(如果稍微次优)。
    • 这是否解决了我每次创建一个新对象时,它都有相同的种子的问题?如果是这样,为什么我的方法不起作用?我阅读了上面的链接,它说 c++11 不允许我们写 std::mt19937 engine{std::random_device{}}; 但我认为类似的方式可以编译和运行。也许我们在“允许”上模棱两可。另外,我不希望在构造函数之外编写代码。
    • @Taylor 你写的基本上是std::mt19937 engine{std::random_device{}()};(注意额外的())。这应该适用于大多数平台,但为种子使用高分辨率时钟时间可能更便携,因为允许std::random_device 是确定性的。
    • @MilesBudnek 当你说std::random_device 被允许是确定性的时,你是说某些系统在实例化m_rd 然后调用m_rd(),并一遍又一遍地这样做五秒之间,会产生相同的数字吗?而对于这个解决方案,为什么我们不能在构造函数内部做unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
    • @Taylor 可以。它通常不会在具有良好随机性来源的系统上(如 /dev/urandom 或 CryptGenRandom),但标准允许它是具有固定种子的 PRNG。如果是,每个std::random_device 对象将产生相同的数字序列。在构造函数中使用std::chrono::high_resolution_clock::now().time_since_epoch().count() 将起作用。
    猜你喜欢
    • 2010-12-19
    • 2016-02-21
    • 2012-01-13
    • 2014-02-22
    • 2023-03-18
    • 2020-01-10
    • 2016-01-06
    • 2015-03-01
    • 2017-06-10
    相关资源
    最近更新 更多