`rand()`的用处 - 或谁应该调用`srand()`?

时间:2014-10-10 07:33:06

标签: c++ c random srand

背景:我在代码中使用rand()std::rand()std::random_shuffle()和其他函数进行科学计算。为了能够重现我的结果,我总是明确指定随机种子,并通过srand()进行设置。直到最近,当我发现libxml2还会在第一次使用时懒散地调用srand() - 这是在我早期的srand()调用之后。

我填写了bug report to libxml2 about its srand() call,但我得到了答案:

  

首先初始化libxml2。   这是一个完全合法的电话会议。你应该   不要指望没有其他人打电话给srand(),并且手册页无处可寻   说明应该避免多次使用srand()

这实际上是我现在的问题。如果一般政策是每个lib可以/应该/将会调用srand(),我可以/也可以在这里和那里调用它,我真的不知道它是如何有用的。或者rand()如何有用呢?

这就是为什么我认为,一般(不成文)策略是没有lib应该调用srand()并且应用程序应该在开始时只调用它一次。 (不考虑多线程。我想在这种情况下,你应该使用不同的东西。)

我还尝试研究其他库实际调用的srand(),但我没有发现任何。有没有?

我目前的解决方法是这个丑陋的代码:

{
    // On the first call to xmlDictCreate,
    // libxml2 will initialize some internal randomize system,
    // which calls srand(time(NULL)).
    // So, do that first call here now, so that we can use our
    // own random seed.
    xmlDictPtr p = xmlDictCreate();
    xmlDictFree(p);
}

srand(my_own_seed);

可能唯一干净的解决方案是根本不使用它,只使用我自己的随机生成器(可能通过C++11 <random>)。但这不是真正的问题。 问题是,谁应该致电srand(),如果每个人都这样做,rand()如何有用?

4 个答案:

答案 0 :(得分:33)

请改用新的<random>标头。它允许多个引擎实例,使用不同的算法,更重要的是,为您提供独立的种子。

[编辑] 回答&#34;有用的&#34;部分,rand生成随机数字。这有什么好处。如果您需要细粒度控制(包括可重复性),您不仅应该拥有已知种子,还应该使用已知算法。 srand充其量只会为您提供固定的种子,因此无论如何都不是完整的解决方案。

答案 1 :(得分:26)

嗯,显而易见的是其他人已经说过几次,使用新的C ++ 11生成器。不过,我会以不同的理由重述它 你使用输出进行科学计算,而rand通常会实现一个相当差的生成器(同时,许多主流实现使用MT19937除了糟糕的状态恢复之外不是这样的不好,但你无法保证特定的算法,至少有一个主流编译器仍然使用非常差的LCG。)

不要用糟糕的发电机进行科学计算。如果你在你的手机上拍摄一些愚蠢的游戏小鸟,如果你的随机数字中有超平面这样的东西并不重要,那么科学模拟的重要时间就很重要了。不要使用坏的发电机。唐&#39;吨

重要提示:std::random_shuffle(包含两个参数的版本)实际上可能会调用rand,如果您正在使用该版本,这是一个需要注意的陷阱,即使您使用了<random>中的新C ++ 11生成器。

关于实际问题,调用srand两次(甚至更频繁)是没有问题的。原则上,您可以根据需要随时调用它,它所做的只是更改种子,以及随后的伪随机序列。我想知道为什么一个XML库根本不想调用它,但是他们在他们的响应中是正确的,他们这样做并不是非法的。但这也无关紧要 确保唯一重要的是要么你不关心任何特定的伪随机序列(也就是说,任何序列都会这样做,你&#39;对再现一个确切的序列不感兴趣),你是最后一个呼叫srand的人,它将覆盖任何先前的呼叫。

那就是说,实现你自己的发电机具有良好的统计特性和足够长的时间段,在3-5行代码中也不是那么难,只需要小心。除了速度之外,主要优点是您可以精确控制您的状态以及修改状态 您不太可能需要比2 128 更长的时段,因为实际消耗这么多数字的时间绝对是禁止的。在2 128 期间,每个周期消耗一个一个的3GHz计算机将持续10 21 年,因此不太多平均寿命的人类问题。即使假设你运行模拟的超级计算机的速度要快一万亿倍,你的盛大孩子也不会活着看到这个时期的结束。
就像2 19937 这样的时期而言,这是现有的&#34;最先进的技术。发电机交付真的很荒谬,如果你问我,那就试图在错误的一端改进发电机(确保它们在统计上更加坚固并且从最坏的情况中迅速恢复时,它会更好 - 案件状态等)。但当然,意见可能会有所不同。

This site列出了几个带有实现的快速生成器。他们的xorshift发生器结合了一个加法或乘法步骤和一个小的(从2到64个机器字)滞后,这导致快速和高质量的发生器(还有一个测试套件,以及网站& #39;作者也写了几篇关于这个主题的论文。我自己修改其中一个(2字128位版本移植到64位,相应地修改了三元组)。

答案 2 :(得分:8)

这个问题正在C ++ 11的随机数生成中解决,即你可以创建一个类的实例:

std::default_random_engine e1

允许您完全控制从对象e1生成的随机数(与libxml中使用的任何内容相对)。一般的经验法则是使用新构造,因为您可以独立生成随机数。

Very good documentation

为了解决您的顾虑 - 我还认为在像libxml这样的库中调用srand()是不好的做法。但是,更多的是srand()rand()并不是设计用于您尝试使用它们的上下文 - 当您需要一些随机数时它们就足够了,就像libxml那样。但是,当您需要可重复性并确保自己独立于其他人时,新的<random>标题是您的最佳选择。所以,总而言之,我认为这不是图书馆方面的好习惯,但很难责怪他们这样做。此外,我无法想象他们会改变这种情况,因为其他十亿个软件可能依赖它。

答案 3 :(得分:6)

这里真正的答案是,如果您想确保您的随机数序列不被其他人的代码更改,您需要一个对您的工作而言属于私密的随机数字上下文。请注意,调用srand只是其中的一小部分。例如,如果在其他调用rand的库中调用某个函数,它也会破坏您的随机数序列。

换句话说,如果您希望基于随机数生成从代码中获得可预测的行为,则需要将其与使用随机数的任何其他代码完全分开。

其他人建议使用C ++ 11随机数生成,这是一种解决方案。

在Linux和其他兼容的库上,您还可以使用rand_r,它将指向unsigned int的指针指向用于该序列的种子。因此,如果您初始化seed变量,然后将其用于对rand_r的所有调用,则会为您的代码生成唯一的序列。这当然仍然是旧的rand生成器,只是一个单独的种子。我的意思是,你可以很容易地做到这样的事情:

int myrand()
{
   static unsigned int myseed = ... some initialization of your choice ...;
   return rand_r(&myseed);
}

并且只需拨打myrand而不是std::rand(并且应该可以使用随机生成器参数的std::random_shuffle