Open tbxfreeware opened 2 months ago
Here is a short program you can run to verify that this bug does, indeed, exist.
// main.cpp
#include <cassert>
#include <iostream>
#include "pcg_random.hpp"
using state_type = pcg32::state_type;
state_type arbitrary_stream()
{
// Choose an arbitrary stream which is different from
// the stream established by the default constructor.
pcg32 e;
state_type const default_stream{ e.stream() };
state_type const clear_msb{ static_cast<state_type>(-1LL) >> 1 };
state_type const arbitrary_stream{ ~default_stream & clear_msb };
assert(default_stream != arbitrary_stream);
e.set_stream(arbitrary_stream);
assert(e.stream() == arbitrary_stream);
return arbitrary_stream;
}
//----------------------------------------------------------------------
bool seed_default_resets_stream()
{
pcg32 e;
state_type const default_stream{ e.stream() };
state_type const another_stream{ arbitrary_stream() };
assert(another_stream != default_stream);
e.set_stream(another_stream);
assert(e.stream() == another_stream);
e.seed();
assert(e.stream() == default_stream);
return e.stream() == default_stream;
}
//----------------------------------------------------------------------
bool seed_itype_resets_stream()
{
pcg32 e;
state_type const default_stream{ e.stream() };
state_type const another_stream{ arbitrary_stream() };
assert(another_stream != default_stream);
e.set_stream(another_stream);
assert(e.stream() == another_stream);
state_type const arbitrary_seed{ 1u };
e.seed(arbitrary_seed);
assert(e.stream() == default_stream);
return e.stream() == default_stream;
}
//----------------------------------------------------------------------
int main()
{
std::cout << "Test pcg_random issue 94"
"\nhttps://github.com/imneme/pcg-cpp/issues/94"
"\n"
"\nGiven a `pcg32` engine named `e`, does calling `e.seed()` "
"\nreset the stream to the value used by the default constructor? "
"\n"
"\n Answer: " << (seed_default_resets_stream() ? "yes" : "no") <<
"\n"
"\n"
"\nGiven a `pcg32` engine named `e`, and an arbitrary seed "
"\nnamed `s`, does calling `e.seed(s)` reset the stream to "
"\nthe value used by the default constructor? "
"\n"
"\n Answer: " << (seed_itype_resets_stream() ? "yes" : "no") <<
"\n\n";
}
// end file: main.cpp
Here is the output:
Test pcg_random issue 94
https://github.com/imneme/pcg-cpp/issues/94
Given a `pcg32` engine named `e`, does calling `e.seed()`
reset the stream to the value used by the default constructor?
Answer: yes
Given a `pcg32` engine named `e`, and an arbitrary seed
named `s`, does calling `e.seed(s)` reset the stream to
the value used by the default constructor?
Answer: yes
With
pcg32
andpcg64
, the streams you select are discarded when you call eitherseed()
orseed(itype)
.When the
stream_mixin
isspecific_stream
, functionsseed()
andseed(itype)
have the—perhaps unintended—side effect of resetting the stream to(default_increment<itype>() >> 1)
. The functionseed(itype)
is the one which takes a seed of typeitype
.This is a consequence of the placement-new technique used by the seeding routines.
Rather than directly updating engine state, which would involve "conditioning" the seed in the same way as the constructor, the PCG seeding routines construct a new engine, on top of the existing one, using "placement-new" syntax. The actual seeding is performed by the engine constructor.
The engine created when the user invokes either
seed()
orseed(itype)
reinstalls the default stream, overwriting any stream that may have been selected by the user.I view this as a bug, but perhaps it is intentional. Users, however, will be surprised, when they call a function to set the seed, and discover that the stream has changed.
If asked, I would recommend ditching the placement-new allocation, in favor of letting the seeding functions install the new seed "manually." This could be accomplished by isolating the "stirring/conditioning" of the seed in its own function.
Here is the one I am using:
With this function in hand, the seeding routines are a breeze. The default argument below, allows a single function to replace both
seed()
andseed(itype)
.A similar change to the constructor accomplishes the same thing, while making its code more readable.