哪些C ++随机数引擎具有O(1)丢弃功能?

时间:2017-11-13 11:46:06

标签: c++11 random complexity-theory

从C ++ 11开始,有许多std随机数引擎。他们实现的成员函数之一是void discard(int long long z),它跳过z随机生成的数字。此功能的复杂性在www.cplusplus.com(http://www.cplusplus.com/reference/random/mersenne_twister_engine/discard/

上以O(z)给出

但是,在www.cppreference.com(http://en.cppreference.com/w/cpp/numeric/random/mersenne_twister_engine/discard)上有一条说明

  

对于某些引擎,已知“快速跳跃”算法,这种算法正在推进   没有计算的许多步骤(数百万的顺序)的状态   中间状态转换。

我如何知道哪些引擎丢弃的实际成本是O(1)?

3 个答案:

答案 0 :(得分:3)

好吧,如果你使用预先计算的跳跃点,O(1)将适用于现有的每一个RNG。请记住,有一些算法可能比O(z)好,但不是O(1) - 比如说O(log 2 z)。

如果我们谈论跳到任意点,事情会变得有趣。例如,对于linear congruential generator,已知O(log 2 z)跳跃算法,基于F. Brown的论文,"随机数生成随机数,&# 34;跨。上午。核酸研究SOC。 (1994年11月)。代码示例为here

C ++ 11标准中有LCG RNG,不确定在特定实现中前进的速度有多快(http://en.cppreference.com/w/cpp/numeric/random/linear_congruential_engine

PCG family RNG共享相同的财产,我相信

答案 1 :(得分:3)

事实是,std::linear_congruential_engine<UIntType,a,c,m>::discard(unsigned long long z)肯定可以非常有效地实施。它大部分等效于a乘幂z的幂m(对于零和非零c而言)-这意味着大多数基本软件实现都在O(log(z % phi(m))) UIntType乘法(素数为phi(m)=m-1,一般为<m),可以通过并行硬件求幂算法实现更快的执行速度。

^^^注意,因为O(log(z % phi(m)))在某种程度上O(1)log2(z % phi(m)) < log2(m) < sizeof(UIntType)*CHAR_BIT-尽管实际上它更像O(log(z))

对于其他大多数引擎的discard函数,可能也有满足O(P(size of state))约束(P(x)-一些低阶多项式函数,最有可能是1+epsilon阶的高效算法甚至更小的x*log(x),因为log(z) < sizeof(unsigned long long)*CHAR_BIT可以被视为常量)。

出于某种未知原因 C ++标准(自ISO / IEC 14882:2017开始)不需要以比仅以更有效的方式实现discard z operator()()调用任何PRNG引擎,包括绝对允许使用此引擎的

就我个人而言,这令人困惑,毫无意义。这直接与先前的C ++原则矛盾,后者仅就性能而言仅对合理的功能进行标准化-例如尽管使用std::list<T>::operator[](size_type n)迭代器operator++() n次很容易,但 begin()很自然,因为{{1 }}执行时间会使该函数在实践中成为不合理的选择。由于这个显而易见的原因,O(n)a[n]不是强制性序列容器要求的一部分,而是可选序列容器操作的一部分。为什么在世界范围内,a.at(n)是强制性随机数引擎要求的一部分,而不是某些具有足够复杂性要求的可选操作部分条目,例如e.discard(z)O(size of state)? / p>

更令人困惑的是在我的GCC中实际找到了这个现实世界的实现:

O(P(size of state))

再一次,我们除了自己实现所需的功能外别无选择...

答案 2 :(得分:0)

我认为这些事情根本不存在。我的启发式结论是O(1)跳转RNG本质上是一个哈希,具有所有这暗示的含义(例如,它可能根本不是“好” RNG)。

但是,即使您询问的是O(log z),我也看不到STL中实现了这一点。我在GCC中discard能够使用的所有grep函数都是简单的循环。

      discard(unsigned long long __z)
      {
        for (; __z != 0ULL; --__z)
          (*this)();
      }

这不仅令人难过,而且还会引起误解,因为discard仅在有有效方法的情况下才存在。

唯一不琐碎的是mersenne(如下),但仍然是O(z)

    discard(unsigned long long __z)
    {
      while (__z > state_size - _M_p)
    {
      __z -= state_size - _M_p;
      _M_gen_rand();
    }
      _M_p += __z;
    }

Boost的Mersenne具有跳过功能,但仅在大于10000000(默认值)的跳过时才被调用。这已经告诉我,跳过在计算上非常繁琐(即使它是O(log z))。 https://www.boost.org/doc/libs/1_72_0/boost/random/mersenne_twister.hpp

最后,对于线性同余,Trust显然具有有效的discard,但仅在c=0的情况下有效。 (我不确定这是否会使它的用处不大。) https://thrust.github.io/doc/classthrust_1_1random_1_1linear__congruential__engine_aec05b19d2a85d02f1ff437791ea4dd68.html#aec05b19d2a85d02f1ff437791ea4dd68