通过构造函数错误地播种 Mersenne Twister
Incorrectly seeding Mersenne Twister via constructor
我的构造函数有什么问题?每次我调用一个应该生成随机数的函数(大约每五秒一次)时,它都会生成相同的数字。每个调用实例化下面这些对象之一。我以为我是用 m_rd
的 operator()
调用的输出随机播种 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())
{
}
因为这个 example, you have to seed it and 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
通常适用于此类事情,但可能并非在每个平台上都适用。虽然大多数平台的标准库根据一些底层 OS 随机功能(即 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())}
{}
};
如您所见,这远非简单,而且仍不完美。有关播种和随机数生成器的更多信息,请参阅 these blog posts,然后您会想到您想要的。
我的构造函数有什么问题?每次我调用一个应该生成随机数的函数(大约每五秒一次)时,它都会生成相同的数字。每个调用实例化下面这些对象之一。我以为我是用 m_rd
的 operator()
调用的输出随机播种 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())
{
}
因为这个 example, you have to seed it and 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
通常适用于此类事情,但可能并非在每个平台上都适用。虽然大多数平台的标准库根据一些底层 OS 随机功能(即 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())}
{}
};
如您所见,这远非简单,而且仍不完美。有关播种和随机数生成器的更多信息,请参阅 these blog posts,然后您会想到您想要的。