pcg-cpp
pcg-cpp copied to clipboard
Four consecutive seeds give the same RNG when using pcg64_fast or pcg32_fast
Four consecutive seeds give identical random numbers for the pcg64_fast or pcg32_fast generators. For example pcg64_fast(0), pcg64_fast(1), pcg64_fast(2), and pcg64_fast(3) return identical states and thus identical random numbers.
See output of my test script (example.zip):
checking seed 0 to 9 using pcg32_fast..
0 0 3614609610 1032979711
1 0 3614609610 1032979711
2 0 3614609610 1032979711
3 0 3614609610 1032979711
4 0 517360375 2849173575
5 0 517360375 2849173575
6 0 517360375 2849173575
7 0 517360375 2849173575
8 0 3092770245 231179071
9 0 3092770245 231179071
checking seed 0 to 9 using pcg64_fast..
0 972365100324636832 3152476261539479119 4963323010661987954
1 972365100324636832 3152476261539479119 4963323010661987954
2 972365100324636832 3152476261539479119 4963323010661987954
3 972365100324636832 3152476261539479119 4963323010661987954
4 8681540523656324337 8610569632533855969 15689244027466136655
5 8681540523656324337 8610569632533855969 15689244027466136655
6 8681540523656324337 8610569632533855969 15689244027466136655
7 8681540523656324337 8610569632533855969 15689244027466136655
8 4621899917499390773 4965021727069323010 11849989275254633783
9 4621899917499390773 4965021727069323010 11849989275254633783
checking seed 0 to 9 using pcg32..
0 3894649422 2055130073 2315086854
1 1412771199 1791099446 124312908
2 3461116586 2338043315 3191287740
3 3053290573 2880989984 3620796526
4 78042152 3239916909 598054463
5 338748765 3035781544 893179696
6 2079632860 1396738611 3112634576
7 1273465047 4201302492 1760530922
8 1658943282 3425254359 1380307894
9 3396683593 298473608 3727095581
checking seed 0 to 9 using pcg64..
0 74029666500212977 8088122161323000979 16521829690994476282
1 16246141021062200314 13888980485107364105 1444523129010881979
2 15283991013767829887 16012073048563605758 18041754508879526868
3 4571984878009979848 4956887517419763765 10963035626649002760
4 8213965343793293256 14634166037439189285 7351202335824653137
5 12828375142235852897 3043909115430366615 12212956655512261919
6 14744360861768679421 7731410906820487750 9990277316766610057
7 2314236103276969522 16242248372244286679 9455988229017472731
8 6146847088143149122 105960750376085417 8469200379157136437
9 4176210987077628350 5147113462634521278 7929888373037025941
It appears that that seed = 4*n+i for i in {0,1,2,3} gives identical results for a fixed n for the two fast variants. Additionally, the 32 bit fast variant seems to start at zero.
Checking whether the problem remains for higher seeds:
pcg64_fast: All checked generators are memory identical for fixed seed // 4
pcg32_fast: All checked generators are memory identical for fixed seed // 4
You are right!
A workaround solution is to add 4 instead of adding 1 when you increase the seed value.
Here are notes I made when I discovered this problem a while back. Note: The "fast" engines are the ones that use no_stream as their stream_mixin.
// When the stream_mixin is no_stream, the constructor engine(seed)
// overwrites the two low-order bits of seed, setting them both to
// one. Thus, the two low-order bits are effectively discarded.
// One surprising consequence is that adding 1 to a seed may not
// change the seed used by PCG.
//
// For example, suppose you are launching multiple threads, and
// for whatever reason, have decided to use no_stream. As each
// thread is launched, you add one to the seed used on the
// previous thread. Whoops! Three out of every four engines
// are using a duplicate seed that was already used before.
//
// As an alternative, it may be better to discard the two high-
// order bits when seeding is performed. Rather than
//
// state = seed | 3; // discard two low-order bits
//
// consider using
//
// state = (seed << 2) | 3; // discard the two high-order bits
//
// The biggest problem I see with this alternative occurs when
// "toy" 8-bit engines are being used. Discarding any bits, be
// they low-order or high, could be problematic, depending exactly
// what the value of the seed is in the first place!
//
// A second alternative would be to "hash" or "stir" or "condition"
// the user-supplied seed BEFORE discarding any bits. If done
// effectively, there might not be much difference between
// discarding low-order bits and discarding high-order bits.
//
// I tend to favor the second alternative. Frankly, I never understood why
// the seed in a no_stream was not "conditioned" in the first place.