`rand()`的用途 – 或者是谁应该调用`srand()`?

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

我向libxml2提交了关于srand()调用的错误报告 ,但是我得到了答案:

然后初始化libxml2。 这是一个完全合法的电话来自图书馆。 你不应该指望没有其他人调用srand() ,而手册页也没有提到应该避免多次使用srand()

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

这就是为什么我认为,一般(不成文)的政策是没有lib应该调用srand() ,应用程序应该在一开始只调用一次。 (不考虑multithreading,我想在这种情况下,无论如何你都应该使用不同的东西。)

我也试图研究一下其他库实际上叫做srand() ,但我没有find任何。 有没有?

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

 { // 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); 

也许唯一干净的解决scheme就是不使用它,只使用我自己的随机生成器(也许通过C ++ 11 <random> )。 但这不是真正的问题。 问题是,谁应该调用srand() ,如果每个人都这样做,那么rand()是如何有用的呢?

改用新的<random>标题。 它允许多个引擎实例,使用不同的algorithm,更重要的是为你,独立的种子。

为了回答“有用的”部分, rand生成随机数字。 这就是它的好处。 如果你需要细粒度的控制,包括可重复性,你不仅应该有一个已知的种子,但已知的algorithm。 srand充其量给你一个固定的种子,所以这不是一个完整的解决scheme。

那么,很明显的事情已经被其他人陈述了几次,使用新的C + + 11发电机。 不过,我正在重申它的原因。
您使用输出进行科学计算 ,并且rand通常实现一个相当差的生成器(同时,许多主stream实现使用MT19937,除了不良状态恢复不是那么糟糕,但是你不能保证特定的algorithm,并且至less有一个主stream编译器仍然使用非常差的LCG)。

不要使用差的发生器进行科学计算。 如果你在你的手机上做一些傻小鸟射击小鸟的话,在你的随机数字中是否有超平面这样的东西并不重要,但是对于科学模拟而言,这是一个重要的时间。 永远不要使用不良的发生器。 别。

重要提示: std::random_shuffle (带有两个参数的版本)实际上可能会调用rand ,即使您使用的是rand ,也是一个陷阱,即使您使用了<random>

关于实际的问题,调用srand两次(甚至更多)是没有问题的。 你原则上可以随心所欲地调用它,所做的只是改变种子,从而改变随后的伪随机序列。 我想知道为什么一个XML库想要调用它,但是他们的回答是正确的,这样做并不是非法的。 但是这也没有关系。
唯一重要的事情是要么你不关心得到任何特定的伪随机序列(也就是说,任何序列都可以,你没有兴趣重现一个确切的序列), 或者你是最后一个叫srand ,这将覆盖任何以前的电话。

也就是说,在3-5行代码中实现具有良好统计特性和足够长的周期的自己的发生器并不是那么困难,只需要小心一点。 主要优点(除了速度)是,你确切地控制你的状态在哪里,谁来修改它。
由于纯粹的禁止时间来实际消耗这么多的数字,你不可能需要比2 128更长的时间。 一个3GHz的计算机每个周期消耗一个数字,在2个128 周期内运行10 21年,所以对于平均寿命的人来说没有太大的问题。 即使假设你运行模拟的超级计算机速度快了几万倍,你的盛大的孩子也不会活着看到这个时期的结束。
就目前的“最先进的”发电机所提供的时间来说,如2 19937年这样的时期实际上是荒谬的,那就是如果你问我,试图改善发电机的错误结果(最好是确保它们统计稳定,来自最坏情况的状态等)。 但是当然,这里的意见可能会有所不同。

这个网站列出了几个带有实现的快速生成器。 它们是xorshift生成器,结合了一个加法或乘法步骤,以及一个小的(从2到64个机器字)滞后,从而生成快速和高质量的生成器(还有一个testing套件,网站的作者写了几个关于这个问题的论文也是如此)。 我正在使用其中的一个修改(2字128位版本移植到64位,相应地修改了三倍的移位)。

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

 std::default_random_engine e1 

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

非常好的文档

为了解决您的担忧 – 我也认为在像libxml这样的库中调用srand()会是一个糟糕的做法。 然而, srand()rand()并不是devise用来在上下文中使用它们 – 当你只需要一些随机数时就足够了,就像libxml一样。 但是,当你需要重复性,并确保你是独立的,新的<random>头是你的方式。 所以,总而言之,我认为这不是一个很好的做法,但很难责怪他们这样做。 另外,我无法想象它们会改变这种情况,因为其他十亿个软件可能依赖于它。

这里真正的答案是,如果你想确定你的随机数字序列没有被别人的代码改变,你需要一个随机数字的上下文,这是私人的工作。 请注意,调用srand只是其中的一小部分。 例如,如果您在其他一些调用rand库中调用某个函数,它也会破坏您的随机数序列。

换句话说,如果你想从你的代码中预测行为,基于随机数生成,它需要完全独立于任何其他使用随机数的代码。

其他人build议使用C ++ 11随机数生成,这是一个解决scheme。

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

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

并简单地调用myrand而不是std::rand (并且应该可以工作到std::random_shuffle ,它需要一个随机的生成器参数)