Hello community, here is the log from the commit of package ghc-splitmix for openSUSE:Factory checked in at 2019-08-13 13:15:29 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ghc-splitmix (Old) and /work/SRC/openSUSE:Factory/.ghc-splitmix.new.9556 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ghc-splitmix" Tue Aug 13 13:15:29 2019 rev:2 rq:721037 version:0.0.3 Changes: -------- --- /work/SRC/openSUSE:Factory/ghc-splitmix/ghc-splitmix.changes 2019-04-03 09:27:33.151802167 +0200 +++ /work/SRC/openSUSE:Factory/.ghc-splitmix.new.9556/ghc-splitmix.changes 2019-08-13 13:15:31.929501982 +0200 @@ -1,0 +2,12 @@ +Wed Jul 31 02:03:22 UTC 2019 - psim...@suse.com + +- Update splitmix to version 0.0.3. + # 0.0.3 + + - Add `System.Random.SplitMix32` module + - Add `bitmaskWithRejection32` and `bitmaskWithRejection64` module + - Add `nextWord32`, `nextTwoWord32` and `nextFloat` + - Add `random` flag, dropping dependency on `random` + (breaks things, e.g. `QuickCheck`, when disabled). + +------------------------------------------------------------------- Old: ---- splitmix-0.0.2.tar.gz New: ---- splitmix-0.0.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ghc-splitmix.spec ++++++ --- /var/tmp/diff_new_pack.4T9tgC/_old 2019-08-13 13:15:33.429501635 +0200 +++ /var/tmp/diff_new_pack.4T9tgC/_new 2019-08-13 13:15:33.445501631 +0200 @@ -19,7 +19,7 @@ %global pkg_name splitmix %bcond_with tests Name: ghc-%{pkg_name} -Version: 0.0.2 +Version: 0.0.3 Release: 0 Summary: Fast Splittable PRNG License: BSD-3-Clause @@ -32,6 +32,7 @@ BuildRequires: ghc-rpm-macros BuildRequires: ghc-time-devel %if %{with tests} +BuildRequires: ghc-HUnit-devel BuildRequires: ghc-async-devel BuildRequires: ghc-base-compat-batteries-devel BuildRequires: ghc-bytestring-devel ++++++ splitmix-0.0.2.tar.gz -> splitmix-0.0.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/splitmix-0.0.2/Changelog.md new/splitmix-0.0.3/Changelog.md --- old/splitmix-0.0.2/Changelog.md 2001-09-09 03:46:40.000000000 +0200 +++ new/splitmix-0.0.3/Changelog.md 2001-09-09 03:46:40.000000000 +0200 @@ -1,3 +1,11 @@ +# 0.0.3 + +- Add `System.Random.SplitMix32` module +- Add `bitmaskWithRejection32` and `bitmaskWithRejection64` module +- Add `nextWord32`, `nextTwoWord32` and `nextFloat` +- Add `random` flag, dropping dependency on `random` + (breaks things, e.g. `QuickCheck`, when disabled). + # 0.0.2 - Support back to GHC-7.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/splitmix-0.0.2/bench/Bench.hs new/splitmix-0.0.3/bench/Bench.hs --- old/splitmix-0.0.2/bench/Bench.hs 2001-09-09 03:46:40.000000000 +0200 +++ new/splitmix-0.0.3/bench/Bench.hs 2001-09-09 03:46:40.000000000 +0200 @@ -9,6 +9,7 @@ import qualified System.Random.TF as TF import qualified System.Random.TF.Instances as TF import qualified System.Random.SplitMix as SM +import qualified System.Random.SplitMix32 as SM32 ------------------------------------------------------------------------------- -- List @@ -31,6 +32,9 @@ splitMixList :: Word64 -> [Int] splitMixList w64 = genListN $ SM.mkSMGen w64 +splitMix32List :: Word64 -> [Int] +splitMix32List w64 = genListN $ SM32.mkSMGen $ fromIntegral w64 + ------------------------------------------------------------------------------- -- Tree ------------------------------------------------------------------------------- @@ -57,6 +61,9 @@ splitMixTree :: Word64 -> T.Tree Int splitMixTree w64 = genTreeN $ SM.mkSMGen w64 +splitMix32Tree :: Word64 -> T.Tree Int +splitMix32Tree w64 = genTreeN $ SM32.mkSMGen $ fromIntegral w64 + ------------------------------------------------------------------------------- -- List Word64 ------------------------------------------------------------------------------- @@ -78,6 +85,9 @@ splitMixList64 :: Word64 -> [Word64] splitMixList64 w64 = genListN64 SM.nextWord64 $ SM.mkSMGen w64 +splitMix32List64 :: Word64 -> [Word64] +splitMix32List64 w64 = genListN64 SM32.nextWord64 $ SM32.mkSMGen $ fromIntegral w64 + ------------------------------------------------------------------------------- -- Tree Word64 ------------------------------------------------------------------------------- @@ -105,6 +115,9 @@ splitMixTree64 :: Word64 -> T.Tree Word64 splitMixTree64 w64 = genTreeN64 SM.nextWord64 $ SM.mkSMGen w64 +splitMix32Tree64 :: Word64 -> T.Tree Word64 +splitMix32Tree64 w64 = genTreeN64 SM32.nextWord64 $ SM32.mkSMGen $ fromIntegral w64 + ------------------------------------------------------------------------------- -- Main ------------------------------------------------------------------------------- @@ -112,23 +125,27 @@ main :: IO () main = defaultMain [ bgroup "list" - [ bench "random" $ nf randomList 42 - , bench "tf-random" $ nf tfRandomList 42 - , bench "splitmix" $ nf splitMixList 42 + [ bench "random" $ nf randomList 42 + , bench "tf-random" $ nf tfRandomList 42 + , bench "splitmix" $ nf splitMixList 42 + , bench "splitmix32" $ nf splitMix32List 42 ] , bgroup "tree" - [ bench "random" $ nf randomTree 42 - , bench "tf-random" $ nf tfRandomTree 42 - , bench "splitmix" $ nf splitMixTree 42 + [ bench "random" $ nf randomTree 42 + , bench "tf-random" $ nf tfRandomTree 42 + , bench "splitmix" $ nf splitMixTree 42 + , bench "splitmix32" $ nf splitMix32Tree 42 ] , bgroup "list 64" - [ bench "random" $ nf randomList64 42 - , bench "tf-random" $ nf tfRandomList64 42 - , bench "splitmix" $ nf splitMixList64 42 + [ bench "random" $ nf randomList64 42 + , bench "tf-random" $ nf tfRandomList64 42 + , bench "splitmix" $ nf splitMixList64 42 + , bench "splitmix32" $ nf splitMix32List64 42 ] , bgroup "tree 64" - [ bench "random" $ nf randomTree64 42 - , bench "tf-random" $ nf tfRandomTree64 42 - , bench "splitmix" $ nf splitMixTree64 42 + [ bench "random" $ nf randomTree64 42 + , bench "tf-random" $ nf tfRandomTree64 42 + , bench "splitmix" $ nf splitMixTree64 42 + , bench "splitmix32" $ nf splitMix32Tree64 42 ] ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/splitmix-0.0.2/bench/Range.hs new/splitmix-0.0.3/bench/Range.hs --- old/splitmix-0.0.2/bench/Range.hs 1970-01-01 01:00:00.000000000 +0100 +++ new/splitmix-0.0.3/bench/Range.hs 2001-09-09 03:46:40.000000000 +0200 @@ -0,0 +1,109 @@ +-- http://www.pcg-random.org/posts/bounded-rands.html +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE CPP #-} +module Main where + +import Data.Bits +import Data.Bits.Compat +import Data.List (unfoldr) +import Data.Word (Word32, Word64) + +import qualified System.Random as R +import qualified System.Random.SplitMix32 as SM + +#if defined(__GHCJS__) +#else +import System.Clock (Clock (Monotonic), getTime, toNanoSecs) +import Text.Printf (printf) +#endif + +main :: IO () +main = do + gen <- SM.newSMGen + + bench gen (\g h -> R.randomR (0, pred h) g) + bench gen classicMod + bench gen intMult + bench gen bitmaskWithRejection + +bench :: g -> (g -> Word32 -> (Word32, g)) -> IO () +bench gen next = do + print $ take 70 $ unfoldr (\g -> Just (next g 10)) gen + clocked $ do + let x = sumOf next gen + print x + +sumOf :: (g -> Word32 -> (Word32, g)) -> g -> Word32 +sumOf next = go 0 2 + where + go !acc !n g | n > 0xfffff = acc + | otherwise = let (w, g') = next g n in go (acc + w) (succ n) g' + +classicMod :: SM.SMGen -> Word32 -> (Word32, SM.SMGen) +classicMod g h = + let (w32, g') = SM.nextWord32 g in (w32 `mod` h, g') + + +-- @ +-- uint32_t bounded_rand(rng_t& rng, uint32_t range) { +-- uint32_t x = rng(); +-- uint64_t m = uint64_t(x) * uint64_t(range); +-- return m >> 32; +-- } +-- @ +-- +intMult :: SM.SMGen -> Word32 -> (Word32, SM.SMGen) +intMult g h = + (fromIntegral $ (fromIntegral w32 * fromIntegral h :: Word64) `shiftR` 32, g') + where + (w32, g') = SM.nextWord32 g + +-- @ +-- uint32_t bounded_rand(rng_t& rng, uint32_t range) { +-- uint32_t mask = ~uint32_t(0); +-- --range; +-- mask >>= __builtin_clz(range|1); +-- uint32_t x; +-- do { +-- x = rng() & mask; +-- } while (x > range); +-- return x; +-- } +-- @@ +bitmaskWithRejection :: SM.SMGen -> Word32 -> (Word32, SM.SMGen) +bitmaskWithRejection g0 range = go g0 + where + mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) + go g = let (x, g') = SM.nextWord32 g + x' = x .&. mask + in if x' >= range + then go g' + else (x', g') + +------------------------------------------------------------------------------- +-- Poor man benchmarking with GHC and GHCJS +------------------------------------------------------------------------------- + +clocked :: IO () -> IO () +#if defined(__GHCJS__) +clocked action = do + start + action + stop + +foreign import javascript unsafe + "console.time('loop');" + start :: IO () + +foreign import javascript unsafe + "console.timeEnd('loop');" + stop :: IO () +#else +clocked action = do + start <- getTime Monotonic + action + end <- getTime Monotonic + printf "loop: %.03fms\n" + $ fromIntegral (toNanoSecs (end - start)) + / (1e6 :: Double) +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/splitmix-0.0.2/bench/SimpleSum.hs new/splitmix-0.0.3/bench/SimpleSum.hs --- old/splitmix-0.0.2/bench/SimpleSum.hs 1970-01-01 01:00:00.000000000 +0100 +++ new/splitmix-0.0.3/bench/SimpleSum.hs 2001-09-09 03:46:40.000000000 +0200 @@ -0,0 +1,43 @@ +{-# LANGUAGE CPP #-} +module Main (main) where + +import System.Environment (getArgs) +import Data.List (foldl') +import Data.Word (Word32) + +import qualified System.Random as R +import qualified System.Random.SplitMix as SM +import qualified System.Random.SplitMix32 as SM32 + +newGen :: a -> (a -> g) -> IO g -> IO g +#if 0 +newGen _ _ new = new +#else +newGen seed mk _ = return (mk seed) +#endif + +main :: IO () +main = do + putStrLn "Summing randoms..." + getArgs >>= \args -> case args of + "splitmix" : _ -> newGen 33 SM.mkSMGen SM.newSMGen >>= \g -> print $ benchSum g SM.nextTwoWord32 + "splitmix32" : _ -> newGen 33 SM32.mkSMGen SM32.newSMGen >>= \g -> print $ benchSum g SM32.nextTwoWord32 + "random" : _ -> R.newStdGen >>= \g -> print $ benchSum g randomNextTwoWord32 + + -- after Closure Compiler getArgs return [] always? + -- _ -> newGen 33 SM.mkSMGen SM.newSMGen >>= \g -> print $ benchSum g SM.nextTwoWord32 + _ -> newGen 33 SM32.mkSMGen SM32.newSMGen >>= \g -> print $ benchSum g SM32.nextTwoWord32 + + +benchSum :: g -> (g -> (Word32, Word32, g)) -> Word32 +benchSum g next = foldl' (+) 0 $ take 10000000 $ unfoldr2 next g + +-- | Infinite unfoldr with two element generator +unfoldr2 :: (s -> (a, a, s)) -> s -> [a] +unfoldr2 f = go where + go s = let (x, y, s') = f s in x : y : go s' + +randomNextTwoWord32 :: R.StdGen -> (Word32, Word32, R.StdGen) +randomNextTwoWord32 s0 = (x, y, s2) where + (x, s1) = R.random s0 + (y, s2) = R.random s1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/splitmix-0.0.2/splitmix.cabal new/splitmix-0.0.3/splitmix.cabal --- old/splitmix-0.0.2/splitmix.cabal 2001-09-09 03:46:40.000000000 +0200 +++ new/splitmix-0.0.3/splitmix.cabal 2001-09-09 03:46:40.000000000 +0200 @@ -1,6 +1,6 @@ cabal-version: >=1.10 name: splitmix -version: 0.0.2 +version: 0.0.3 synopsis: Fast Splittable PRNG description: Pure Haskell implementation of SplitMix described in @@ -30,30 +30,48 @@ license-file: LICENSE maintainer: Oleg Grenrus <oleg.gren...@iki.fi> bug-reports: https://github.com/phadej/splitmix#issues -category: System +category: System, Random build-type: Simple tested-with: - GHC ==8.6.4 || ==8.4.4 || ==8.2.2 || ==8.0.2 || ==7.10.3 || ==7.8.4 || ==7.6.3 || ==7.4.2 || ==7.2.2 || ==7.0.4 + GHC ==8.8.1 || ==8.6.5 || ==8.4.4 || ==8.2.2 || ==8.0.2 || ==7.10.3 || ==7.8.4 || ==7.6.3 || ==7.4.2 || ==7.2.2 || ==7.0.4 extra-source-files: README.md Changelog.md +flag optimised-mixer + description: Use JavaScript for mix32 + manual: True + default: False + +flag random + description: Providen RandomGen SMGen instance + manual: True + default: True + library default-language: Haskell2010 ghc-options: -Wall - hs-source-dirs: src - exposed-modules: System.Random.SplitMix + hs-source-dirs: src src-compat + other-modules: Data.Bits.Compat + exposed-modules: + System.Random.SplitMix + System.Random.SplitMix32 -- dump-core -- build-depends: dump-core -- ghc-options: -fplugin=DumpCore -fplugin-opt DumpCore:core-html build-depends: - base >=4.3 && <4.13 + base >=4.3 && <4.14 , deepseq >=1.3.0.0 && <1.5 - , random >=1.0 && <1.2 - , time >=1.2.0.3 && <1.9 + , time >=1.2.0.3 && <1.10 + + if flag(random) + build-depends: random >=1.0 && <1.2 + + if flag(optimised-mixer) + cpp-options: -DOPTIMISED_MIX32=1 source-repository head type: git @@ -73,6 +91,41 @@ , splitmix , tf-random >=0.5 && <0.6 +benchmark simple-sum + type: exitcode-stdio-1.0 + default-language: Haskell2010 + ghc-options: -Wall + hs-source-dirs: bench + main-is: SimpleSum.hs + build-depends: + base + , random + , splitmix + +benchmark range + type: exitcode-stdio-1.0 + default-language: Haskell2010 + ghc-options: -Wall + hs-source-dirs: bench src-compat + main-is: Range.hs + other-modules: Data.Bits.Compat + build-depends: + base + , clock >=0.8 && <0.9 + , random + , splitmix + +test-suite examples + type: exitcode-stdio-1.0 + default-language: Haskell2010 + ghc-options: -Wall + hs-source-dirs: tests + main-is: Examples.hs + build-depends: + base + , HUnit ==1.3.1.2 || >=1.6.0.0 && <1.7 + , splitmix + test-suite montecarlo-pi type: exitcode-stdio-1.0 default-language: Haskell2010 @@ -83,9 +136,19 @@ base , splitmix -test-suite splitmix-dieharder +test-suite montecarlo-pi-32 type: exitcode-stdio-1.0 default-language: Haskell2010 + ghc-options: -Wall + hs-source-dirs: tests + main-is: SplitMixPi32.hs + build-depends: + base + , splitmix + +test-suite splitmix-dieharder + default-language: Haskell2010 + type: exitcode-stdio-1.0 ghc-options: -Wall -threaded -rtsopts hs-source-dirs: tests main-is: Dieharder.hs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/splitmix-0.0.2/src/System/Random/SplitMix.hs new/splitmix-0.0.3/src/System/Random/SplitMix.hs --- old/splitmix-0.0.2/src/System/Random/SplitMix.hs 2001-09-09 03:46:40.000000000 +0200 +++ new/splitmix-0.0.3/src/System/Random/SplitMix.hs 2001-09-09 03:46:40.000000000 +0200 @@ -1,8 +1,6 @@ -- | -- /SplitMix/ is a splittable pseudorandom number generator (PRNG) that is quite fast. -- --- Guy L. Steele, Jr., Doug Lea, and Christine H. Flood. 2014. Fast splittable pseudorandom number generators. /In Proceedings of the 2014 ACM International Conference on Object Oriented Programming Systems Languages & Applications/ (OOPSLA '13). ACM, New York, NY, USA, 453-472. DOI: <https://doi.org/10.1145/2660193.2660195> --- -- Guy L. Steele, Jr., Doug Lea, and Christine H. Flood. 2014. -- Fast splittable pseudorandom number generators. In Proceedings -- of the 2014 ACM International Conference on Object Oriented @@ -28,17 +26,22 @@ -- but GHC-7.0 and GHC-7.2 have slow implementation, as there -- are no native 'popCount'. -- -{-# LANGUAGE CPP #-} -{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE CPP #-} #if __GLASGOW_HASKELL__ >= 702 -{-# LANGUAGE Trustworthy #-} +{-# LANGUAGE Trustworthy #-} #endif module System.Random.SplitMix ( SMGen, nextWord64, + nextWord32, + nextTwoWord32, nextInt, nextDouble, + nextFloat, splitSMGen, + -- * Generation + bitmaskWithRejection32, + bitmaskWithRejection64, -- * Initialisation mkSMGen, initSMGen, @@ -49,24 +52,19 @@ ) where import Control.DeepSeq (NFData (..)) -import Data.Bits (shiftL, shiftR, xor, (.|.)) +import Data.Bits (complement, shiftL, shiftR, xor, (.&.), (.|.)) +import Data.Bits.Compat (countLeadingZeros, popCount, zeroBits) import Data.IORef (IORef, atomicModifyIORef, newIORef) import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Word (Word32, Word64) -import System.CPUTime (cpuTimePrecision, getCPUTime) import System.IO.Unsafe (unsafePerformIO) +#ifdef MIN_VERSION_random import qualified System.Random as R +#endif -#if MIN_VERSION_base(4,5,0) -import Data.Bits (popCount) -#else -import Data.Bits ((.&.)) -popCount :: Word64 -> Int -popCount = go 0 - where - go !c 0 = c - go c w = go (c+1) (w .&. (w - 1)) -- clear the least significant +#if !__GHCJS__ +import System.CPUTime (cpuTimePrecision, getCPUTime) #endif -- $setup @@ -98,12 +96,12 @@ -- instance Read SMGen where readsPrec d r = readParen (d > 10) (\r0 -> - [ (SMGen seed gamma, r3) - | ("SMGen", r1) <- lex r0 - , (seed, r2) <- readsPrec 11 r1 - , (gamma, r3) <- readsPrec 11 r2 - , odd gamma - ]) r + [ (SMGen seed gamma, r3) + | ("SMGen", r1) <- lex r0 + , (seed, r2) <- readsPrec 11 r1 + , (gamma, r3) <- readsPrec 11 r2 + , odd gamma + ]) r ------------------------------------------------------------------------------- -- Operations @@ -119,6 +117,20 @@ where seed' = seed + gamma +-- | Generate 'Word32' by truncating 'nextWord64'. +-- +-- @since 0.0.3 +nextWord32 :: SMGen -> (Word32, SMGen) +nextWord32 g = (fromIntegral w64, g') where + (w64, g') = nextWord64 g + +-- | Generate two 'Word32'. +-- +-- @since 0.0.3 +nextTwoWord32 :: SMGen -> (Word32, Word32, SMGen) +nextTwoWord32 g = (fromIntegral $ w64 `shiftR` 32, fromIntegral w64, g') where + (w64, g') = nextWord64 g + -- | Generate an 'Int'. nextInt :: SMGen -> (Int, SMGen) nextInt g = case nextWord64 g of @@ -133,6 +145,16 @@ nextDouble g = case nextWord64 g of (w64, g') -> (fromIntegral (w64 `shiftR` 11) * doubleUlp, g') +-- | Generate a 'Float' in @[0, 1)@ range. +-- +-- >>> take 8 $ map (printf "%0.3f") $ unfoldr (Just . nextFloat) (mkSMGen 1337) :: [String] +-- ["0.057","0.089","0.237","0.383","0.680","0.320","0.826","0.007"] +-- +-- @since 0.0.3 +nextFloat :: SMGen -> (Float, SMGen) +nextFloat g = case nextWord32 g of + (w32, g') -> (fromIntegral (w32 `shiftR` 8) * floatUlp, g') + -- | Split a generator into a two uncorrelated generators. splitSMGen :: SMGen -> (SMGen, SMGen) splitSMGen (SMGen seed gamma) = @@ -148,13 +170,14 @@ goldenGamma :: Word64 goldenGamma = 0x9e3779b97f4a7c15 +floatUlp :: Float +floatUlp = 1.0 / fromIntegral (1 `shiftL` 24 :: Word32) + doubleUlp :: Double doubleUlp = 1.0 / fromIntegral (1 `shiftL` 53 :: Word64) -- Note: in JDK implementations the mix64 and mix64variant13 -- (which is inlined into mixGamma) are swapped. --- --- I have no idea if swapping them affects statistical properties. mix64 :: Word64 -> Word64 mix64 z0 = -- MurmurHash3Mixer @@ -168,6 +191,8 @@ mix64variant13 z0 = -- Better Bit Mixing - Improving on MurmurHash3's 64-bit Finalizer -- http://zimbry.blogspot.fi/2011/09/better-bit-mixing-improving-on.html + -- + -- Stafford's Mix13 let z1 = shiftXorMultiply 30 0xbf58476d1ce4e5b9 z0 -- MurmurHash3 mix constants z2 = shiftXorMultiply 27 0x94d049bb133111eb z1 z3 = shiftXor 31 z2 @@ -190,6 +215,41 @@ shiftXorMultiply n k w = shiftXor n w * k ------------------------------------------------------------------------------- +-- Generation +------------------------------------------------------------------------------- + +-- | /Bitmask with rejection/ method of generating subrange of 'Word32'. +-- +-- @since 0.0.3 +bitmaskWithRejection32 :: Word32 -> SMGen -> (Word32, SMGen) +bitmaskWithRejection32 range = go where + mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) + go g = let (x, g') = nextWord32 g + x' = x .&. mask + in if x' >= range + then go g' + else (x', g') + +-- | /Bitmask with rejection/ method of generating subrange of 'Word64'. +-- +-- @bitmaskWithRejection64 w64@ generates random numbers in closed-open +-- range of @[0, w64)@. +-- +-- >>> take 20 $ unfoldr (Just . bitmaskWithRejection64 5) (mkSMGen 1337) +-- [3,1,4,1,2,3,1,1,0,3,4,2,3,0,2,3,3,4,1,0] +-- +-- @since 0.0.3 +bitmaskWithRejection64 :: Word64 -> SMGen -> (Word64, SMGen) +bitmaskWithRejection64 range = go where + mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) + go g = let (x, g') = nextWord64 g + x' = x .&. mask + in if x' >= range + then go g' + else (x', g') + + +------------------------------------------------------------------------------- -- Initialisation ------------------------------------------------------------------------------- @@ -235,15 +295,21 @@ mkSeedTime :: IO Word64 mkSeedTime = do now <- getPOSIXTime - cpu <- getCPUTime let lo = truncate now :: Word32 - hi = fromIntegral (cpu `div` cpuTimePrecision) :: Word32 +#if __GHCJS__ + let hi = lo +#else + cpu <- getCPUTime + let hi = fromIntegral (cpu `div` cpuTimePrecision) :: Word32 +#endif return $ fromIntegral hi `shiftL` 32 .|. fromIntegral lo ------------------------------------------------------------------------------- -- System.Random ------------------------------------------------------------------------------- +#ifdef MIN_VERSION_random instance R.RandomGen SMGen where next = nextInt split = splitSMGen +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/splitmix-0.0.2/src/System/Random/SplitMix32.hs new/splitmix-0.0.3/src/System/Random/SplitMix32.hs --- old/splitmix-0.0.2/src/System/Random/SplitMix32.hs 1970-01-01 01:00:00.000000000 +0100 +++ new/splitmix-0.0.3/src/System/Random/SplitMix32.hs 2001-09-09 03:46:40.000000000 +0200 @@ -0,0 +1,311 @@ +-- | +-- /SplitMix/ is a splittable pseudorandom number generator (PRNG) that is quite fast. +-- +-- This is 32bit variant (original one is 32 bit). +-- +-- You __really don't want to use this one__. +-- +-- Note: This module supports all GHCs since GHC-7.0.4, +-- but GHC-7.0 and GHC-7.2 have slow implementation, as there +-- are no native 'popCount'. +-- +{-# LANGUAGE CPP #-} +#if __GLASGOW_HASKELL__ >= 702 +{-# LANGUAGE Trustworthy #-} +#endif +module System.Random.SplitMix32 ( + SMGen, + nextWord32, + nextWord64, + nextTwoWord32, + nextInt, + nextDouble, + nextFloat, + splitSMGen, + -- * Generation + bitmaskWithRejection32, + bitmaskWithRejection64, + -- * Initialisation + mkSMGen, + initSMGen, + newSMGen, + seedSMGen, + seedSMGen', + unseedSMGen, + ) where + +import Control.DeepSeq (NFData (..)) +import Data.Bits (complement, shiftL, shiftR, xor, (.&.), (.|.)) +import Data.Bits.Compat + (countLeadingZeros, finiteBitSize, popCount, zeroBits) +import Data.IORef (IORef, atomicModifyIORef, newIORef) +import Data.Time.Clock.POSIX (getPOSIXTime) +import Data.Word (Word32, Word64) +import System.IO.Unsafe (unsafePerformIO) + +#ifdef MIN_VERSION_random +import qualified System.Random as R +#endif + +#if !__GHCJS__ +import System.CPUTime (cpuTimePrecision, getCPUTime) +#endif + +-- $setup +-- >>> import Text.Read (readMaybe) +-- >>> import Data.List (unfoldr) +-- >>> import Text.Printf (printf) + +------------------------------------------------------------------------------- +-- Generator +------------------------------------------------------------------------------- + +-- | SplitMix generator state. +data SMGen = SMGen {-# UNPACK #-} !Word32 {-# UNPACK #-} !Word32 -- seed and gamma; gamma is odd + deriving Show + +instance NFData SMGen where + rnf (SMGen _ _) = () + +-- | +-- +-- >>> readMaybe "SMGen 1 1" :: Maybe SMGen +-- Just (SMGen 1 1) +-- +-- >>> readMaybe "SMGen 1 2" :: Maybe SMGen +-- Nothing +-- +-- >>> readMaybe (show (mkSMGen 42)) :: Maybe SMGen +-- Just (SMGen 142593372 1604540297) +-- +instance Read SMGen where + readsPrec d r = readParen (d > 10) (\r0 -> + [ (SMGen seed gamma, r3) + | ("SMGen", r1) <- lex r0 + , (seed, r2) <- readsPrec 11 r1 + , (gamma, r3) <- readsPrec 11 r2 + , odd gamma + ]) r + +------------------------------------------------------------------------------- +-- Operations +------------------------------------------------------------------------------- + +-- | Generate a 'Word32'. +-- +-- >>> take 3 $ map (printf "%x") $ unfoldr (Just . nextWord32) (mkSMGen 1337) :: [String] +-- ["e0cfe722","a6ced0f0","c3a6d889"] +-- +nextWord32 :: SMGen -> (Word32, SMGen) +nextWord32 (SMGen seed gamma) = (mix32 seed', SMGen seed' gamma) + where + seed' = seed + gamma + +-- | Generate a 'Word64', by generating to 'Word32's. +nextWord64 :: SMGen -> (Word64, SMGen) +nextWord64 s0 = (fromIntegral w0 `shiftL` 32 .|. fromIntegral w1, s2) + where + (w0, s1) = nextWord32 s0 + (w1, s2) = nextWord32 s1 + +-- | Generate two 'Word32'. +nextTwoWord32 :: SMGen -> (Word32, Word32, SMGen) +nextTwoWord32 s0 = (w0, w1, s2) where + (w0, s1) = nextWord32 s0 + (w1, s2) = nextWord32 s1 + +-- | Generate an 'Int'. +nextInt :: SMGen -> (Int, SMGen) +nextInt g | isBigInt = int64 + | otherwise = int32 + where + int32 = case nextWord32 g of + (w, g') -> (fromIntegral w, g') + int64 = case nextWord64 g of + (w, g') -> (fromIntegral w, g') + +isBigInt :: Bool +isBigInt = finiteBitSize (undefined :: Int) > 32 + +-- | Generate a 'Double' in @[0, 1)@ range. +-- +-- >>> take 8 $ map (printf "%0.3f") $ unfoldr (Just . nextDouble) (mkSMGen 1337) :: [String] +-- ["0.878","0.764","0.063","0.845","0.262","0.490","0.176","0.544"] +-- +nextDouble :: SMGen -> (Double, SMGen) +nextDouble g = case nextWord64 g of + (w64, g') -> (fromIntegral (w64 `shiftR` 11) * doubleUlp, g') + +-- | Generate a 'Float' in @[0, 1)@ range. +-- +-- >>> take 8 $ map (printf "%0.3f") $ unfoldr (Just . nextFloat) (mkSMGen 1337) :: [String] +-- ["0.878","0.652","0.764","0.631","0.063","0.180","0.845","0.645"] +-- +nextFloat :: SMGen -> (Float, SMGen) +nextFloat g = case nextWord32 g of + (w32, g') -> (fromIntegral (w32 `shiftR` 8) * floatUlp, g') + +-- | Split a generator into a two uncorrelated generators. +splitSMGen :: SMGen -> (SMGen, SMGen) +splitSMGen (SMGen seed gamma) = + (SMGen seed'' gamma, SMGen (mix32 seed') (mixGamma seed'')) + where + seed' = seed + gamma + seed'' = seed' + gamma + +------------------------------------------------------------------------------- +-- Algorithm +------------------------------------------------------------------------------- + +-- | (1 + sqrt 5) / 2 * (2 ^^ bits) +goldenGamma :: Word32 +goldenGamma = 0x9e3779b9 + +floatUlp :: Float +floatUlp = 1.0 / fromIntegral (1 `shiftL` 24 :: Word32) + +doubleUlp :: Double +doubleUlp = 1.0 / fromIntegral (1 `shiftL` 53 :: Word64) + +#if defined(__GHCJS__) && defined(OPTIMISED_MIX32) +-- JavaScript Foreign Function Interface +-- https://github.com/ghcjs/ghcjs/blob/master/doc/foreign-function-interface.md + +foreign import javascript unsafe + "var x0 = $1 ^ $1 >>> 16; var x1 = x0 & 0xffff; var x2 = (((x0 >>> 16 & 0xffff) * 0x0000ca6b + x1 * 0x000085eb & 0xffff) << 16) + x1 * 0x0000ca6b; var x3 = x2 ^ x2 >>> 13; var x4 = x3 & 0xffff; var x5 = (((x3 >>> 16 & 0xffff) * 0x0000ae35 + x4 * 0x0000c2b2 & 0xffff) << 16) + x4 * 0x0000ae35; $r = (x5 ^ x5 >>> 16) | 0;" + mix32 :: Word32 -> Word32 + +foreign import javascript unsafe + "var x0 = $1 ^ $1 >>> 16; var x1 = x0 & 0xffff; var x2 = (((x0 >>> 16 & 0xffff) * 0x00006ccb + x1 * 0x000069ad & 0xffff) << 16) + x1 * 0x00006ccb; var x3 = x2 ^ x2 >>> 13; var x4 = x3 & 0xffff; var x5 = (((x3 >>> 16 & 0xffff) * 0x0000b5b3 + x4 * 0x0000cd9a & 0xffff) << 16) + x4 * 0x0000b5b3; $r = (x5 ^ x5 >>> 16) | 0;" + mix32variant13 :: Word32 -> Word32 + +#else +mix32 :: Word32 -> Word32 +mix32 z0 = + -- MurmurHash3Mixer 32bit + let z1 = shiftXorMultiply 16 0x85ebca6b z0 + z2 = shiftXorMultiply 13 0xc2b2ae35 z1 + z3 = shiftXor 16 z2 + in z3 + +-- used only in mixGamma +mix32variant13 :: Word32 -> Word32 +mix32variant13 z0 = + -- See avalanche "executable" + let z1 = shiftXorMultiply 16 0x69ad6ccb z0 + z2 = shiftXorMultiply 13 0xcd9ab5b3 z1 + z3 = shiftXor 16 z2 + in z3 + +shiftXor :: Int -> Word32 -> Word32 +shiftXor n w = w `xor` (w `shiftR` n) + +shiftXorMultiply :: Int -> Word32 -> Word32 -> Word32 +shiftXorMultiply n k w = shiftXor n w * k +#endif + +mixGamma :: Word32 -> Word32 +mixGamma z0 = + let z1 = mix32variant13 z0 .|. 1 -- force to be odd + n = popCount (z1 `xor` (z1 `shiftR` 1)) + -- see: http://www.pcg-random.org/posts/bugs-in-splitmix.html + -- let's trust the text of the paper, not the code. + in if n >= 12 + then z1 + else z1 `xor` 0xaaaaaaaa + +------------------------------------------------------------------------------- +-- Generation +------------------------------------------------------------------------------- + +-- | /Bitmask with rejection/ method of generating subrange of 'Word32'. +bitmaskWithRejection32 :: Word32 -> SMGen -> (Word32, SMGen) +bitmaskWithRejection32 range = go where + mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) + go g = let (x, g') = nextWord32 g + x' = x .&. mask + in if x' >= range + then go g' + else (x', g') + +-- | /Bitmask with rejection/ method of generating subrange of 'Word64'. +-- +-- @bitmaskWithRejection64 w64@ generates random numbers in closed-open +-- range of @[0, w64)@. +-- +-- >>> take 20 $ unfoldr (Just . bitmaskWithRejection64 5) (mkSMGen 1337) +-- [0,2,4,2,1,4,2,4,2,2,3,0,3,2,2,2,3,1,2,2] +-- +bitmaskWithRejection64 :: Word64 -> SMGen -> (Word64, SMGen) +bitmaskWithRejection64 range = go where + mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) + go g = let (x, g') = nextWord64 g + x' = x .&. mask + in if x' >= range + then go g' + else (x', g') + +------------------------------------------------------------------------------- +-- Initialisation +------------------------------------------------------------------------------- + +-- | Create 'SMGen' using seed and gamma. +-- +-- >>> seedSMGen 2 2 +-- SMGen 2 3 +-- +seedSMGen + :: Word32 -- ^ seed + -> Word32 -- ^ gamma + -> SMGen +seedSMGen seed gamma = SMGen seed (gamma .|. 1) + +-- | Like 'seedSMGen' but takes a pair. +seedSMGen' :: (Word32, Word32) -> SMGen +seedSMGen' = uncurry seedSMGen + +-- | Extract current state of 'SMGen'. +unseedSMGen :: SMGen -> (Word32, Word32) +unseedSMGen (SMGen seed gamma) = (seed, gamma) + +-- | Preferred way to deterministically construct 'SMGen'. +-- +-- >>> mkSMGen 42 +-- SMGen 142593372 1604540297 +-- +mkSMGen :: Word32 -> SMGen +mkSMGen s = SMGen (mix32 s) (mixGamma (s + goldenGamma)) + +-- | Initialize 'SMGen' using system time. +initSMGen :: IO SMGen +initSMGen = fmap mkSMGen mkSeedTime + +-- | Derive a new generator instance from the global 'SMGen' using 'splitSMGen'. +newSMGen :: IO SMGen +newSMGen = atomicModifyIORef theSMGen splitSMGen + +theSMGen :: IORef SMGen +theSMGen = unsafePerformIO $ initSMGen >>= newIORef +{-# NOINLINE theSMGen #-} + +mkSeedTime :: IO Word32 +mkSeedTime = do + now <- getPOSIXTime + let lo = truncate now :: Word32 +#if __GHCJS__ + let hi = lo +#else + cpu <- getCPUTime + let hi = fromIntegral (cpu `div` cpuTimePrecision) :: Word32 +#endif + return $ fromIntegral hi `shiftL` 32 .|. fromIntegral lo + +------------------------------------------------------------------------------- +-- System.Random +------------------------------------------------------------------------------- + +#ifdef MIN_VERSION_random +instance R.RandomGen SMGen where + next = nextInt + split = splitSMGen +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/splitmix-0.0.2/src-compat/Data/Bits/Compat.hs new/splitmix-0.0.3/src-compat/Data/Bits/Compat.hs --- old/splitmix-0.0.2/src-compat/Data/Bits/Compat.hs 1970-01-01 01:00:00.000000000 +0100 +++ new/splitmix-0.0.3/src-compat/Data/Bits/Compat.hs 2001-09-09 03:46:40.000000000 +0200 @@ -0,0 +1,45 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE BangPatterns #-} +module Data.Bits.Compat ( + popCount, + zeroBits, + finiteBitSize, + countLeadingZeros, + ) where + +import Data.Bits + +#if !MIN_VERSION_base(4,7,0) +#define FiniteBits Bits +#endif + +#if !MIN_VERSION_base(4,5,0) +popCount :: Bits a => a -> Int +popCount = go 0 + where + go !c 0 = c + go c w = go (c+1) (w .&. (w - 1)) -- clear the least significant +{-# INLINE popCount #-} +#endif + +#if !MIN_VERSION_base(4,7,0) +zeroBits :: Bits a => a +zeroBits = clearBit (bit 0) 0 +{-# INLINE zeroBits #-} + +finiteBitSize :: Bits a => a -> Int +finiteBitSize = bitSize +{-# INLINE finiteBitSize #-} +#endif + +#if !MIN_VERSION_base(4,8,0) +countLeadingZeros :: FiniteBits b => b -> Int +countLeadingZeros x = (w-1) - go (w-1) + where + go i | i < 0 = i -- no bit set + | testBit x i = i + | otherwise = go (i-1) + + w = finiteBitSize x +{-# INLINE countLeadingZeros #-} +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/splitmix-0.0.2/tests/Dieharder.hs new/splitmix-0.0.3/tests/Dieharder.hs --- old/splitmix-0.0.2/tests/Dieharder.hs 2001-09-09 03:46:40.000000000 +0200 +++ new/splitmix-0.0.3/tests/Dieharder.hs 2001-09-09 03:46:40.000000000 +0200 @@ -8,6 +8,7 @@ import Control.Concurrent.QSem import Control.DeepSeq (force) +import Control.Monad (when) import Data.Bits (shiftL, (.|.)) import Data.Char (isSpace) import Data.List (isInfixOf, unfoldr) @@ -17,7 +18,7 @@ import Foreign.Ptr (castPtr) import GHC.IO.Exception (IOErrorType (..), IOException (..)) import System.Environment (getArgs) -import System.IO (Handle, hGetContents) +import System.IO (Handle, hGetContents, stdout) import Text.Printf (printf) import qualified Control.Concurrent.Async as A @@ -27,6 +28,7 @@ import qualified Data.Vector.Storable.Mutable as MSV import qualified System.Process as Proc import qualified System.Random.SplitMix as SM +import qualified System.Random.SplitMix32 as SM32 import qualified System.Random.TF as TF import qualified System.Random.TF.Gen as TF import qualified System.Random.TF.Init as TF @@ -37,18 +39,26 @@ if null args then return () else do - (cmd, runs, conc, seed, test, _help) <- parseArgsIO args $ (,,,,,) + (cmd, runs, conc, seed, test, raw, _help) <- parseArgsIO args $ (,,,,,,) <$> arg <*> optDef "-n" 1 <*> optDef "-j" 1 <*> opt "-s" <*> opt "-d" + <*> flag "-r" <*> flag "-h" + let run :: RunType g + run | raw = runRaw + | otherwise = runManaged + case cmd of "splitmix" -> do g <- maybe SM.initSMGen (return . SM.mkSMGen) seed run test runs conc SM.splitSMGen SM.nextWord64 g + "splitmix32" -> do + g <- maybe SM32.initSMGen (return . SM32.mkSMGen) (fmap fromIntegral seed) + run test runs conc SM32.splitSMGen SM32.nextWord64 g "tfrandom" -> do g <- TF.initTFGen run test runs conc TF.split tfNext64 g @@ -64,19 +74,26 @@ -- Dieharder ------------------------------------------------------------------------------- -run :: Maybe Int +type RunType g = + Maybe Int -> Int -> Int -> (g -> (g, g)) -> (g -> (Word64, g)) -> g - -> IO () -run test runs conc split word gen = do + -> IO () + +runRaw :: RunType g +runRaw _test _runs _conc split word gen = + generate word split gen stdout + +runManaged :: RunType g +runManaged test runs conc split word gen = do qsem <- newQSem conc rs <- A.forConcurrently (take runs $ unfoldr (Just . split) gen) $ \g -> E.bracket_ (waitQSem qsem) (signalQSem qsem) $ - dieharder test (generate word g) + dieharder test (generate word split g) case mconcat rs of Result p w f -> do @@ -84,7 +101,7 @@ printf "PASSED %4d %6.02f%%\n" p (fromIntegral p / total * 100) printf "WEAK %4d %6.02f%%\n" w (fromIntegral w / total * 100) printf "FAILED %4d %6.02f%%\n" f (fromIntegral f / total * 100) -{-# INLINE run #-} +{-# INLINE runManaged #-} dieharder :: Maybe Int -> (Handle -> IO ()) -> IO Result dieharder test gen = do @@ -144,26 +161,29 @@ size :: Int size = 512 -generate :: forall g. (g -> (Word64, g)) -> g -> Handle -> IO () -generate word gen0 h = do +generate + :: forall g. (g -> (Word64, g)) + -> (g -> (g, g)) + -> g -> Handle -> IO () +generate word split gen0 h = do vec <- MSV.new size go gen0 vec where go :: g -> MSV.IOVector Word64 -> IO () go gen vec = do - gen' <- write gen vec 0 + let (g1, g2) = split gen + write g1 vec 0 MSV.unsafeWith vec $ \ptr -> do bs <- BS.unsafePackCStringLen (castPtr ptr, size * 8) BS.hPutStr h bs - go gen' vec + go g2 vec - write :: g -> MSV.IOVector Word64 -> Int -> IO g + write :: g -> MSV.IOVector Word64 -> Int -> IO () write !gen !vec !i = do let (w64, gen') = word gen MSV.unsafeWrite vec i w64 - if i < size - then write gen' vec (i + 1) - else return gen' + when (i < size) $ + write gen' vec (i + 1) {-# INLINE generate #-} ------------------------------------------------------------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/splitmix-0.0.2/tests/Examples.hs new/splitmix-0.0.3/tests/Examples.hs --- old/splitmix-0.0.2/tests/Examples.hs 1970-01-01 01:00:00.000000000 +0100 +++ new/splitmix-0.0.3/tests/Examples.hs 2001-09-09 03:46:40.000000000 +0200 @@ -0,0 +1,15 @@ +module Main (main) where + +import Test.HUnit ((@?=)) + +import qualified System.Random.SplitMix32 as SM32 + +main :: IO () +main = do + let g = SM32.mkSMGen 42 + show g @?= "SMGen 142593372 1604540297" + print g + + let (w32, g') = SM32.nextWord32 g + w32 @?= 1296549791 + show g' @?= "SMGen 1747133669 1604540297" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/splitmix-0.0.2/tests/SplitMixPi.hs new/splitmix-0.0.3/tests/SplitMixPi.hs --- old/splitmix-0.0.2/tests/SplitMixPi.hs 2001-09-09 03:46:40.000000000 +0200 +++ new/splitmix-0.0.3/tests/SplitMixPi.hs 2001-09-09 03:46:40.000000000 +0200 @@ -7,7 +7,7 @@ doubles = unfoldr (Just . nextDouble) monteCarloPi :: SMGen -> Double -monteCarloPi = (4 *) . calc . foldl' accum (P 0 0) . take 10000000 . pairs . doubles +monteCarloPi = (4 *) . calc . foldl' accum (P 0 0) . take 50000000 . pairs . doubles where calc (P n m) = fromIntegral n / fromIntegral m @@ -25,4 +25,3 @@ print (pi :: Double) print pi' print (pi - pi') - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/splitmix-0.0.2/tests/SplitMixPi32.hs new/splitmix-0.0.3/tests/SplitMixPi32.hs --- old/splitmix-0.0.2/tests/SplitMixPi32.hs 1970-01-01 01:00:00.000000000 +0100 +++ new/splitmix-0.0.3/tests/SplitMixPi32.hs 2001-09-09 03:46:40.000000000 +0200 @@ -0,0 +1,27 @@ +module Main (main) where + +import Data.List (unfoldr, foldl') +import System.Random.SplitMix32 + +doubles :: SMGen -> [Float] +doubles = unfoldr (Just . nextFloat) + +monteCarloPi :: SMGen -> Float +monteCarloPi = (4 *) . calc . foldl' accum (P 0 0) . take 50000000 . pairs . doubles + where + calc (P n m) = fromIntegral n / fromIntegral m + + pairs (x : y : xs) = (x, y) : pairs xs + pairs _ = [] + + accum (P n m) (x, y) | x * x + y * y >= 1 = P n (m + 1) + | otherwise = P (n + 1) (m + 1) + +data P = P !Int !Int + +main :: IO () +main = do + pi' <- fmap monteCarloPi newSMGen + print (pi :: Float) + print pi' + print (pi - pi')