This question concerns the origins of temporal correlations one observes with System.Random
when one generates successive randoms from successive seeds (where o
Let's begin by looking at what the code says, and we can get there.
First off, random
as applied to Bool
is equivalent to:
randomB :: StdGen -> (Bool, StdGen)
randomB g = let (i, g') = next g in (i `mod` 2 == 1, g')
In fact, if I replace random
with randomB
where that's appropriate in your program, I get identical results. The point is that for determining booleans, all we care about is whether the next Int
value is even or odd.
Next, let's look at the definition of StdGen
:
data StdGen
= StdGen Int32 Int32
So two Int32
s are the state. Let's see how they're initialized with mkStdGen
and how they're adjusted with next
:
mkStdGen :: Int -> StdGen -- why not Integer ?
mkStdGen s = mkStdGen32 $ fromIntegral s
mkStdGen32 :: Int32 -> StdGen
mkStdGen32 s
| s < 0 = mkStdGen32 (-s)
| otherwise = StdGen (s1+1) (s2+1)
where
(q, s1) = s `divMod` 2147483562
s2 = q `mod` 2147483398
...
stdNext :: StdGen -> (Int, StdGen)
-- Returns values in the range stdRange
stdNext (StdGen s1 s2) = (fromIntegral z', StdGen s1'' s2'')
where z' = if z < 1 then z + 2147483562 else z
z = s1'' - s2''
k = s1 `quot` 53668
s1' = 40014 * (s1 - k * 53668) - k * 12211
s1'' = if s1' < 0 then s1' + 2147483563 else s1'
k' = s2 `quot` 52774
s2' = 40692 * (s2 - k' * 52774) - k' * 3791
s2'' = if s2' < 0 then s2' + 2147483399 else s2'
Note two interesting things:
The way s2
is initialized guarantees that it will be 1 unless you send a very high number to mkStdGen
, in which case it will be 2 (there are fewer than 200 values in the Int32
range that will initialize s2
to 2.
The two halves of the state are kept distinct - the updated s2
depends only on the previous s2
, not on the previous s1
, and vice versa.
As a consequence, if you examine the generators that you get out of a certain fixed number of generations passed to better_mkStdGen
, the second half of their state will always be identical.
Try it by adding this to your program:
print $ map (dropWhile (/= ' ') . show . better_mkStdGen 10) [0 .. 20]
So then the question is, why the mixing function in s1
fails to mix the last bit properly. Note that the way it's written, s1'
and k
will have the same parity as s1
, so the s1
state only has different parity from the previous s1
state if s1'
ends up being less than zero.
At this point I need to handwave a bit and say that the way s1'
is computed means that if two initial values for s1
are close to each other, the two values for s1'
will also be close together, and in general will only be 40014 times as far apart as they were initially, which in the range we allow for s1
makes neighboring values quite likely to end up on the same side of zero.