{-# OPTIONS_HADDOCK not-home #-} {-# LANGUAGE CPP #-} -- | -- This is a port of "Fast Splittable Pseudorandom Number Generators" by Steele -- et. al. [1]. -- -- The paper's algorithm provides decent randomness for most purposes but -- sacrifices cryptographic-quality randomness in favor of speed. The original -- implementation is tested with DieHarder and BigCrush; see the paper for -- details. -- -- This implementation, originally from [2], is a port from the paper. -- -- It also takes in to account the SplittableRandom.java source code in OpenJDK -- v8u40-b25 as well as splittable_random.ml in Jane Street's standard library -- overlay (kernel) v113.33.03, and Random.fs in FsCheck v3. -- -- Other than the choice of initial seed for 'from' this port should be -- faithful. -- -- 1. Guy L. Steele, Jr., Doug Lea, Christine H. Flood -- Fast splittable pseudorandom number generators -- Comm ACM, 49(10), Oct 2014, pp453-472. -- -- 2. Nikos Baxevanis -- https://github.com/moodmosaic/SplitMix/blob/master/SplitMix.hs -- #include "MachDeps.h" module Hedgehog.Internal.Seed ( Seed(..) , random , from , split , nextInteger , nextDouble -- * Internal -- $internal , goldenGamma , nextWord64 , nextWord32 , mix64 , mix64variant13 , mix32 , mixGamma ) where import Control.Monad.IO.Class (MonadIO(..)) import Data.Bifunctor (first) import Data.Bits ((.|.), xor, shiftR, popCount) #if (SIZEOF_HSINT == 8) import Data.Int (Int64) #else import Data.Int (Int32) #endif import Data.Time.Clock.POSIX (getPOSIXTime) import Data.IORef (IORef) import qualified Data.IORef as IORef import Data.Word (Word32, Word64) import System.IO.Unsafe (unsafePerformIO) import System.Random (RandomGen) import qualified System.Random as Random -- | A splittable random number generator. -- data Seed = Seed { seedValue :: !Word64 , seedGamma :: !Word64 -- ^ must be an odd number } deriving (Eq, Ord) instance Show Seed where showsPrec p (Seed v g) = showParen (p > 10) $ showString "Seed " . showsPrec 11 v . showChar ' ' . showsPrec 11 g instance Read Seed where readsPrec p = readParen (p > 10) $ \r0 -> do ("Seed", r1) <- lex r0 (v, r2) <- readsPrec 11 r1 (g, r3) <- readsPrec 11 r2 pure (Seed v g, r3) global :: IORef Seed global = unsafePerformIO $ do -- FIXME use /dev/urandom on posix seconds <- getPOSIXTime IORef.newIORef $ from (round (seconds * 1000)) {-# NOINLINE global #-} -- | Create a random 'Seed' using an effectful source of randomness. -- random :: MonadIO m => m Seed random = liftIO $ IORef.atomicModifyIORef' global split -- | Create a 'Seed' using a 'Word64'. -- from :: Word64 -> Seed from x = Seed (mix64 x) (mixGamma (x + goldenGamma)) -- | A predefined gamma value's needed for initializing the "root" instances of -- 'Seed'. That is, instances not produced by splitting an already existing -- instance. -- -- We choose: the odd integer closest to @2^64/φ@, where @φ = (1 + √5)/2@ is -- the golden ratio. -- goldenGamma :: Word64 goldenGamma = 0x9e3779b97f4a7c15 -- | Get the next value in the SplitMix sequence. -- next :: Seed -> (Word64, Seed) next (Seed v0 g) = let v = v0 + g in (v, Seed v g) -- | Splits a random number generator in to two. -- split :: Seed -> (Seed, Seed) split s0 = let (v0, s1) = next s0 (g0, s2) = next s1 in (s2, Seed (mix64 v0) (mixGamma g0)) -- | Generate a random 'Word64'. -- nextWord64 :: Seed -> (Word64, Seed) nextWord64 s0 = let (v0, s1) = next s0 in (mix64 v0, s1) -- | Generate a random 'Word32'. -- nextWord32 :: Seed -> (Word32, Seed) nextWord32 s0 = let (v0, s1) = next s0 in (mix32 v0, s1) -- | Generate a random 'Integer' in the [inclusive,inclusive] range. -- nextInteger :: Integer -> Integer -> Seed -> (Integer, Seed) nextInteger lo hi = Random.randomR (lo, hi) -- | Generate a random 'Double' in the [inclusive,exclusive) range. -- nextDouble :: Double -> Double -> Seed -> (Double, Seed) nextDouble lo hi = Random.randomR (lo, hi) mix64 :: Word64 -> Word64 mix64 x = let y = (x `xor` (x `shiftR` 33)) * 0xff51afd7ed558ccd z = (y `xor` (y `shiftR` 33)) * 0xc4ceb9fe1a85ec53 in z `xor` (z `shiftR` 33) mix32 :: Word64 -> Word32 mix32 x = let y = (x `xor` (x `shiftR` 33)) * 0xff51afd7ed558ccd z = (y `xor` (y `shiftR` 33)) * 0xc4ceb9fe1a85ec53 in fromIntegral (z `shiftR` 32) mix64variant13 :: Word64 -> Word64 mix64variant13 x = let y = (x `xor` (x `shiftR` 30)) * 0xbf58476d1ce4e5b9 z = (y `xor` (y `shiftR` 27)) * 0x94d049bb133111eb in z `xor` (z `shiftR` 31) mixGamma :: Word64 -> Word64 mixGamma x = let y = mix64variant13 x .|. 1 n = popCount $ y `xor` (y `shiftR` 1) in if n < 24 then y `xor` 0xaaaaaaaaaaaaaaaa else y ------------------------------------------------------------------------ -- RandomGen instances #if (SIZEOF_HSINT == 8) instance RandomGen Seed where next = first fromIntegral . nextWord64 genRange _ = (fromIntegral (minBound :: Int64), fromIntegral (maxBound :: Int64)) split = split #else instance RandomGen Seed where next = first fromIntegral . nextWord32 genRange _ = (fromIntegral (minBound :: Int32), fromIntegral (maxBound :: Int32)) split = split #endif ------------------------------------------------------------------------ -- Internal -- $internal -- -- These functions are exported in case you need them in a pinch, but are not -- part of the public API and may change at any time, even as part of a minor -- update.