Getting Started

RandomDataStreams.jl provides pseudo-random number generators with support for non-overlapping streams and substreams, following the stream/substream model of L'Ecuyer et al. (2002).

Installation

using Pkg
Pkg.add("RandomDataStreams")

or, from a local clone:

using Pkg
Pkg.develop(path = "path/to/RandomDataStreams.jl")

Requirements: Julia ≥ 1.6. The only dependency is the standard library Random.

Choosing a generator

RandomDataStreams.jl implements the full xoshiro/xoroshiro family of Blackman & Vigna plus L'Ecuyer's MRG32k3a. All variants of a xoshiro family share the same linear transition and jump constants; only the output scrambler differs.

TypeFamilyStatePeriodScrambler
Xoroshiro128pxoroshiro128128 bits2^128 − 1s0 + s1
Xoroshiro128ssxoroshiro128128 bits2^128 − 1high bits, **
Xoroshiro128ppxoroshiro128128 bits2^128 − 1high bits, ++
Xoshiro256pxoshiro256256 bits2^256 − 1s0 + s3
Xoshiro256ssxoshiro256256 bits2^256 − 1high bits, **
Xoshiro256ppxoshiro256256 bits2^256 − 1high bits, ++
Xoshiro512pxoshiro512512 bits2^512 − 1s0 + s2
Xoshiro512ssxoshiro512512 bits2^512 − 1high bits, **
Xoshiro512ppxoshiro512512 bits2^512 − 1high bits, ++
MRG32k3acombined MRG (L'Ecuyer)192 bits≈ 2^191native Float64

See Generator Comparison for guidance and benchmarks.

First numbers

using RandomDataStreams

rng = MRG32k3a()      # default seed [12345, ..., 12345]
rand(rng)             # 0.12701112204657714
rand(rng)             # 0.3185275653967945
x = Xoshiro256p(UInt64[1, 2, 3, 4])
rand(x)               # Float64 in [0, 1)

Supported output types

MRG32k3a

  • Float64 (native), plus Float32, Float16 via conversion
  • UInt8, UInt16, UInt32, UInt64, UInt128
  • Int8, Int16, Int32, Int64, Int128 (same bit patterns)
  • Bool
  • Ranges: rand(rng, 1:10) works through the standard Random machinery

Note: integer outputs of MRG32k3a are built from its native ~31-bit resolution; they are not uniform over their full width. Use them for indices, choices and flags rather than cryptography.

Xoshiro256p

  • Float64 (native path), Float32, Float16
  • UInt64
  • Ranges: rand(rng, r::UnitRange{Int64})

Reproducibility

Every generator accepts an explicit seed:

rng = MRG32k3a([42, 1, 2, 3, 4, 5])
x    = Xoshiro256p(UInt64[0xdead, 0xbeef, 0xcafe, 0xbabe])

Seeds are validated by checkseed: a valid MRG32k3a seed has 6 non-negative values; the first three must be < m1 and not all zero, the last three < m2 and not all zero.

The standard Julia interface also works with every generator:

Random.seed!(rng, 42)          # integer seed (splitmix64 expansion)
Random.seed!(rng, [7,7,7,8,8,8])   # explicit MRG32k3a seed (validated)

Full Random API

RandomDataStreams generators are drop-in substitutes for Julia's built-in RNGs: anywhere a function accepts an AbstractRNG, you can pass an RandomDataStreams generator and keep your stream/substream control.

using RandomDataStreams, Random

rng = next_stream!(MRG32k3aGen())     # any RandomDataStreams generator works here

rand(rng, 5)                          # Vector{Float64}, 5 draws
A = rand(rng, Float64, 2, 3)          # 2x3 matrix
z = randn(rng)                        # standard normal
e = randexp(rng)                      # exponential
v = shuffle(rng, collect(1:8))        # shuffled copy
p = randperm(rng, 6)                  # random permutation
buf = Vector{Float64}(undef, 3)
rand!(rng, buf)                       # fill in place
Random.randstring(rng, 10)            # random string

Seeding through the standard interface makes results reproducible:

Random.seed!(rng, 42)
u1 = [rand(rng) for _ in 1:3]
Random.seed!(rng, 42)
u2 = [rand(rng) for _ in 1:3]
u1 == u2                              # true

Only sample requires StatsBase.jl and is out of scope.

Next steps