Since C++11 there are a number of std random number engines. One of the member functions they implement is void discard(int long long z)
which skips over z randomly
Fact is that std::linear_congruential_engine
is definitely possible to implement very efficiently. It is mostly equivalent to exponentiation of a
to the power of z
modulo m
(for both zero and non-zero c
) - it means most basic software implementation executes in O(log(z % phi(m)))
UIntType
multiplications (phi(m)=m-1
for prime number and
^^^NOTE that in a way O(log(z % phi(m)))
is O(1)
because log2(z % phi(m)) < log2(m) < sizeof(UIntType)*CHAR_BIT
- though in practice it is more often like O(log(z))
.
Also probably there are efficient algorithms for most other engines' discard
functions that would satisfy O(P(size of state))
constraint (P(x)
- some low degree polynomial function, most likely 1+epsilon
degree or something even smaller like x*log(x)
, given that log(z) < sizeof(unsigned long long)*CHAR_BIT
may be considered as constant).
Somehow for some unknown reason C++ standard (as of ISO/IEC 14882:2017) does not require to implement discard
in more efficient way than just z
operator()()
calls for any PRNG engine including those that definitely allow this.
To me personally it is baffling and makes no sense whatsoever. This directly contradicts to previous C++ principle to standardize only reasonable functionality in terms of performance - e.g. there is NO std::list
though it is as "easy" as to call operator++()
n
times with begin()
iterator - naturally so because O(n)
execution time would make this function unreasonable choice in practice. For this obvious reason a[n]
and a.at(n)
is not part of mandatory Sequence container requirements but instead is part of Optional sequence container operations. Why in the world then e.discard(z)
is part of mandatory Random number engine requirements instead of some optional operations section entry with adequate complexity requirement like O(size of state)
or O(P(size of state))
?
Even more baffling was to actually find in my GCC this real-world implementation:
void discard(unsigned long long __z)
{
for (; __z != 0ULL; --__z)
(*this)(); //<-- Wait what? Are you kidding me?
}
So once again as it was before we have no other choice than to implement the needed functionality ourselves...