通用随机数生成

时间:2014-10-18 01:52:54

标签: c++ c++11 random

C ++ 11为C rand()引入了一个非常优越的随机数库。在C中,您经常会看到以下代码:

srand(time(0));
rand() % MAX + MIN;

因为time(0)以秒为单位返回当前时间,所以对程序的快速连续调用将产生相同的数字序列。对此的快速解决方法是以纳秒为单位提供种子:

 struct timeval time; 
 gettimeofday(&time,NULL);
 srand((time.tv_sec * 1000) + (time.tv_usec / 1000));

当然,这并没有改变这样一个事实:rand()被普遍认为是坏的,而且优越的替代方案要么是不可移植的(如Linux的random()),要么依赖于第三方库(如Boost) )。

在C ++ 11中,我知道产生良好随机数的最短程序是:

#include <iostream>
#include <random>

int main()
{
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_int_distribution<int> dist(1, 10);
    std::cout << dist(mt);
}

std::random_device是不可移植的,并且不鼓励使用std::default_random_engine,因为它可能会选择一个糟糕的引擎,例如std::rand。事实上,std::random_shuffle已被弃用,因此std::shuffle是首选。一般来说,我看到人们说使用计时代替种子:

std::chrono::high_resolution_clock::now().time_since_epoch().count()

这不仅难以记住,而且在我们想要使用纳秒时看起来更加丑陋:

using namespace std::chrono;
std::mt19937 mt(duration_cast<nanoseconds>(high_resolution_clock::now()
                                      .time_since_epoch()).count());
  • C方法看起来很理想,因为它不需要那么多 样板。

  • random_device最简单,因为它不需要丑陋 单行,即使它是不便携的。

  • mt19937default_random_engine更难记住。

哪种方法最好?

1 个答案:

答案 0 :(得分:1)

(1)知道可用的发电机并选择最适合该工作的发电机

(2)煮种子熵,画一个标准尺度(如256位),打印到日志

(3)将标准种子块转换为适合生成器的seed_seq     有问题并播种genny

关于(1):标准库中的生成器使用起来有点棘手,因为它们都有一些特殊性,并且它们都无法通过TestU01 系统的标准PRNG测试。你必须知道它们的特殊缺陷才能判断它们的适用性。如果做不到这一点,采取mt19937或ranlux,播种他们并希望最好。使用typedef - 您自己的 - 允许您切换和试验不同的gennies。 typeid(rng_t).name()透视伪装并记录实际名称。

关于(2):你不能将原始的,粗糙的熵传递给播种程序;如果你这样做,那么小的种子差异只会导致状态的微小差异。熵必须被煮成一个很好的平滑糊状物,每个位取决于原始输入的每个位的50%概率。这包括输入,如1,2,3,...采取固定标准数量的汤汤使整个事情可管理,如打印到屏幕或登录,以确保必要时的可重复性。毋庸置疑,如果您使用种子数字,如1,2,42,...而不是随机种子,那么您可以将它们打印到日志中,而不是位汤提取物。使用你自己的磨床意味着你不会受到半播种功能的支配,甚至是“缺乏”。像1,2,3等等的种子会给你带来截然不同的发生器状态(序列)。

关于(3):某些生成器 - 如mt19937 - 具有巨大的内部状态,因此您需要相当多地扩展256位(或其他)标准种子。遗憾的是,标准库不包含任何适合此任务的生成器,并且没有适配器可用于将生成器转换为seed_seq。

我使用xorshift*,KISS,跑步(数字食谱)或4x32 Tausworthe(a.k.a。lfsr113),但这些都不在库中。该库也没有任何合适的混合功能(钻头磨床)。

我在a similar topic中发布了杂音混音器的代码 - 一种简单且极其高效的混音功能。我在这里给经典的KISS和Tausworthe,因为我无法在网上找到合适的,干净的参考文献。

struct KISS {  uint32_t a, b, c, d; ... };

uint32_t KISS::cycle ()
{
   a = (a & 0xFFFF) * 36969 + (a >> 16);         // 16-bit MWC, a.k.a. znew()
   b = (b & 0xFFFF) * 18000 + (b >> 16);         // 16-bit MWC, a.k.a. wnew()
   c = c * 69069 + 1234567;                      // 32-bit LCG, a.k.a. CONG()(
   d ^= d << 13;  d ^= d >> 17;  d ^= d << 5;    // 32-bit XorShift a.k.a. SHR3(), corrected

   return (((a << 16) | (b & 0xFFFF)) ^ c) + d;  // mixing function (combiner)
}

Tausworthe合并:

struct LFSR113 {  uint32_t a, b, c, d; ... };

uint32_t LFSR113::cycle ()
{
   a = ((a ^ (a <<  6)) >> 13) ^ ((a & ~0x01) << 18);  // 31 bits
   b = ((b ^ (b <<  2)) >> 27) ^ ((b & ~0x07) <<  2);  // 29 bits
   c = ((c ^ (c << 13)) >> 21) ^ ((c & ~0x0F) <<  7);  // 28 bits
   d = ((d ^ (d <<  3)) >> 12) ^ ((d & ~0x7F) << 13);  // 25 bits

   return a ^ b ^ c ^ d;
}

作为主要生成器,您必须调整禁止的种子(粘性状态),但是对于种子拉伸(制作seed_seq),可以安全地忽略它。有很多替代品,比如使用std :: vector和一个简单的生成器(LCG)来制作一个像样的seed_seq,但我更喜欢尝试,信任和放大彻底分析解决方案,最大限度地提供最少的代码。

这里显示的两个4x32生成器可以使用中国剩余定理进行步进,相反,任何状态都可以映射到整个序列中的唯一点(暂时对轨道之类的东西进行着色)。这使得它们和其他类似的发电机作为一般用途的主要发电机很有吸引力,只要不需要像xorshift1024 *(或mt19937)这样的大型发动机。

在任何情况下,您都需要相当多的代码 - 例如头文件中的模板 - 为了使标准<random>生成器易于使用,使用起来既舒适又安全。但它是100%值得的努力。发电机不是太热,但它们是可维修的;其余的基础设施相当不错,它可以很好地解决你的问题。

P.S。:一些实现(VC ++)允许它将任何生成器传递给seed()函数,这使得事情变得非常简单。其他 - gcc - don,这意味着如果你希望你的代码可以移植,你必须做seed_seq的事情。如果你想要的东西超级简单,只需将你选择的种子通过murmur_mix()传递给种子()然后继续前进。

The wages of fear:一旦你将魔法塞进标题,实际应用就很容易了。

#include "zrbj/rng_wrapper.hpp"
#include <random>
#include <typeinfo>

int main ()
{
   zrbj::seeded<std::mt19937> rng(42);

   std::cout << typeid(rng.wrapped_rng).name() << " -> " << rng();
}       

这会将发生器,42和实际种子打印到日志中,除了将碎片粉碎到碎片并将它们填充到mt19937中。代码一次,向后倾斜,享受。