随机数生成器:是否应将其用作单例

Random Number Generator: Should it be used as a singleton?

本文关键字:单例 是否 随机数生成器      更新时间:2023-10-16

我在多个地方使用随机数,通常在需要时构建一个随机数生成器。目前,我使用Marsaglia Xorshift算法,用当前系统时间为其播种。现在我对这个策略有一些疑问:如果我使用多个生成器,生成器之间的数字的独立性(随机性)取决于种子(相同种子相同的数字)。由于我使用时间(ns)作为种子,并且由于这个时间发生了变化,所以这是可行的,但我想知道是否只使用一个奇异生成器并将其作为单例提供会更好。这会提高随机数的质量吗?

编辑:不幸的是,c++11还不是一个选项

编辑:更具体地说:我并不是说单例可以提高随机数的质量,而是说只有一个生成器被使用和播种。否则,我必须确保不同生成器的种子是独立的(随机的)。极端例子:我用完全相同的数字播种两个生成器->它们之间没有随机性

假设您有几个变量,每个变量都需要是随机的,独立于其他变量,并且会定期从某个随机生成器中重新分配一个新的随机值。这种情况在蒙特卡洛分析和博弈中经常发生(尽管博弈的严谨性远低于蒙特卡洛)。如果存在一个完美的随机数生成器,可以使用它的单个实例化。将生成器中的n伪随机数分配给变量x<1>,下一个随机数分配到变量x2、下一个分配到x3等等,最终在下一个周期返回到变量x1。围绕这里有一个问题:太多的PRNG在独立性测试中失败了,当以这种方式使用时,独立性测试也失败了,有些甚至在单个序列的随机性测试中也失败了。

我的方法是使用单个PRNG生成器作为自包含PRNG的一组N实例的种子生成器。后一种PRNG的每个实例都提供一个变量。所谓自包含,我的意思是PRNG是一个对象,在实例成员中而不是在静态成员或全局变量中维护状态。种子生成器甚至不需要与其他NPRNG来自同一家族。在多个线程同时尝试使用种子生成器的情况下,它只需要是可重入的。然而,在我的使用中,我发现最好在线程开始之前设置PRNG,以确保可重复性。这是一次跑步,一次执行。蒙特卡洛技术通常需要数千次执行,也许更多,也许更多。对于蒙特卡罗,可重复性至关重要。因此,还需要另一个随机种子生成器。这一个种子生成器用于为变量生成N生成器。

可重复性很重要,至少在蒙特卡洛世界是如此。假设长蒙特卡罗模拟的第10234次运行导致了一些大规模故障。如果能看到世界上发生了什么,那就太好了。这可能是统计上的侥幸,也可能是个问题。问题是,在典型的MC设置中,只记录最少量的数据,仅足以计算统计数据。要查看第10234次运行中发生了什么,需要重复该特定情况,但现在要记录所有内容。

当客户端相互关联并且代码需要"独立"的随机数时,您应该使用随机生成器类的相同实例。

当客户端不相互依赖,并且它们是否接收到相同的数字也无关紧要时,可以使用随机生成器类的不同对象。

请注意,对于测试和调试来说,能够再次创建相同的随机数序列是非常有用的。因此,你不应该"随意播种"太多。

我不认为这会增加随机性,但它会减少每次使用随机生成器时创建对象所需的内存。如果这个生成器没有任何特定于实例的设置,您可以创建一个singleton。

由于我使用时间(ns)作为种子,并且由于这个时间发生了变化,所以这是可行的,但我想知道是否只使用一个奇异生成器并将其作为单例使用会更好。

当singleton不是反模式时,这是一个很好的例子。你也可以使用某种反向控制。

这会提高随机数的质量吗?

否。质量取决于生成随机数的算法。你如何使用它是无关紧要的(假设它使用正确)。

编辑:您可以创建某种容器,其中包含RNG类的对象(或使用现有容器)。类似这样的东西:

std::vector< Rng > & RngSingleton()
{
static std::vector< Rng > allRngs( 2 );
return allRngs;
}
struct Rng
{
void SetSeed( const int seen );
int GenerateNumber() const;
//...
};
// ...
RngSingleton().at(0).SetSeed( 55 );
RngSingleton().at(1).SetSeed( 55 );
//...
const auto value1 = RngSingleton().at(0).GenerateNumber;
const auto value2 = RngSingleton().at(1).GenerateNumber;

工厂模式救援。客户端永远不应该担心其依赖关系的实例化规则。它允许交换创建方法。反过来,如果您决定使用不同的算法,您可以交换生成器类,客户端不需要重构。http://www.oodesign.com/factory-pattern.html

--编辑

添加了伪代码(对不起,它不是c++,自从我上次在它工作以来,它已经很久了)

interface PRNG{
function generateRandomNumber():Number;
}
interface Seeder{
function getSeed() : Number;
}
interface PRNGFactory{
function createPRNG():PRNG;
}
class MarsagliaPRNG implements PRNG{
constructor( seed : Number ){
//store seed
}
function generateRandomNumber() : Number{
//do your magic
}
}
class SingletonMarsagliaPRNGFactory implements PRNGFactory{
var seeder : Seeder;
static var prng : PRNG;
function createPRNG() : PRNG{
return prng ||= new MarsagliaPRNG( seeder.getSeed() );
}
}
class TimeSeeder implements Seeder{
function getSeed():Number{
return now();
}
}
//usage:
seeder : Seeder = new TimeSeeder();
prngFactory : PRNGFactory = new SingletonMarsagliaPRNGFactory();
clientA.prng = prngFactory.createPRNG();
clientB.prng = prngFactory.createPRNG();
//both clients got the same instance.

现在最大的优势是,如果您想要/需要更改任何实现细节,那么客户端中就不必更改任何内容。您可以更改播种方法、RNG算法和实例化规则,而不必在任何地方接触任何客户端。