pcg-cpp icon indicating copy to clipboard operation
pcg-cpp copied to clipboard

Four consecutive seeds give the same RNG when using pcg64_fast or pcg32_fast

Open dengesCU opened this issue 3 years ago • 1 comments

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

dengesCU avatar Nov 10 '22 09:11 dengesCU

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.

tbxfreeware avatar Jul 10 '23 21:07 tbxfreeware