Reproducibility

Preserve the stream inputs

Record the package version, generator type, seed or key, purpose IDs, and draw sequence.

Pure calls repeat their result. Continuation calls advance explicit state. Bulk calls follow chained scalar consumption, so changing chunk sizes does not change the underlying stream.

Randomness belongs to logical work, not thread IDs or scheduling order. See Parallel jobs.

PureRNGs implements the Philox and Threefry keyed bijections, but its public streams need not match other packages built on them.

Statistical validation

The Philox and Threefry cores reproduce the Random123 known-answer vectors, and the ChaCha core reproduces the ChaCha8, ChaCha12, and ChaCha20 test vectors. RNGTest BigCrush tests the packed uniform stream in the UInt32 and Float64 lanes. PractRand tests the native word stream and the split and subrng children. The current evidence release collects the latest saved results for all tested configurations. The combined archive and its SHA-256 checksum are available together. The archive contains 34 BigCrush cases and 51 PractRand streams across all 17 named configurations, plus saved GPU validation and performance records. Each PractRand configuration has a 1 TiB root stream and 256 GiB split and subrng streams. These results cover the unchanged streams. Logs retain their original tested revisions and dates and remain outside Git. Publication does not imply that every battery ran again on the latest commit. Hosted CI has CPU tests only, including separate extension jobs.

The published archive covers every named configuration at its recorded revision:

ConfigurationBigCrushPractRand
Eight default-round Philox and Threefry generatorsBoth lanesRoot, split, and subrng
Philox4x32R7, Philox2x64R6, Philox4x64R7, Threefry4x32R12, Threefry4x64R13Both lanesRoot, split, and subrng
ChaCha8, ChaCha12 (also ChaCha), ChaCha20Both lanesRoot, split, and subrng
Threefry2x64R13Both lanesRoot, split, and subrng

All cases pass their recorded campaign acceptance criteria. The archive retains 13 BigCrush diagnostic flags and four non-failing PractRand unusual results. No failures are hidden by repacking. Tests at another round count or with a changed stream do not establish coverage of the current stream. Statistical stream tests do not validate distribution transforms or GPU performance.

Statistical release gate

Before the first package release, complete both batteries for all 17 named configurations above. ChaCha and ChaCha12 denote the same configuration. BigCrush requires both UInt32 and Float64 lanes. PractRand requires a 1 TiB root stream and 256 GiB each of split-eight and subrng-eight streams, with one native word per child in round-robin order. Every stream starts from F(12345) or children derived from that root.

Use the pinned RNGTest/TestU01 and PractRand harnesses and their recorded acceptance criteria. Preserve diagnostic flags as well as failures. Resolve failures and publish the complete logs before release; a pending run is not a pass. Earlier results remain usable only when their tested streams are unchanged. Record the tested revisions and the basis for carrying results forward instead of relabeling old runs as new ones.

Separate bits from floating-point transforms

Primitive integer and uniform floating-point draws agree across supported execution sites.

Normal and exponential draws can differ in their final floating-point transformation across backends. The generator's device selects the formula. A CPU generator evaluates AS241 for normals and a reduced-argument series for exponentials; a CUDA, AMDGPU, or Metal generator evaluates Giles' inverse error function for normals and log for exponentials. The two normal formulas read the same random bits and the same uniform value, but they do not return the same value. The host accuracy checks use limits of 5 ulp for Float32 and 6 ulp for Float64; these are not universal bounds for backend math libraries. A device-placed generator keeps its device when you draw from it on the host, so randn_at(cuda_rng, T, 1) does not equal randn_at(cpu_rng, T, 1). Within one formula, matching random bits still do not force different log or sqrt implementations to return identical values.

Distribution transforms, including affine Uniform(a, b) draws, can also differ when a backend fuses multiply-add operations. This changes floating-point rounding, not the primitive bits or stream advancement.

Weighted sampling and Categorical also depend on cumulative-sum rounding. CUDA can use parallel sums, so selected elements can differ across backends or backend versions. Primitive bits and parent advancement do not change. A WeightTable reuses the same prepared boundaries within its backend.

Reactant permits further compiler-dependent transformations. Its exact limits appear in Differentiation and compilation.

Fixed parent-stream consumption

Package-owned samplers reserve a fixed span of the parent stream for each result. Primitive draws and fixed-work distribution transforms do not retry rejected candidates. Gamma-family draws reserve eight candidates, then continue rejection sampling on a child stream if all eight reject. Their parent advancement stays fixed, but their total random work is not bounded.

Fixed consumption does not promise constant runtime. Allocation, table preparation, backend scheduling, and validation still have costs. Foreign consumers of StatefulRNG may use rejection algorithms.

All draw kinds read one stream of bits at the generator's position. Two draws of different kinds at the same position read the same bits. Advance the generator or derive a new key between them.

See Distributions for each mapping's bit span and finite-grid limits, and Generators and streams for primitive widths and per-key capacities.

Failure behavior

Eager package-owned fills check deterministic contract errors before writing. Exhaustion does not silently wrap the state.

A draw that outruns the stream throws StreamExhausted, which argument validation never throws. Catch it to tell exhaustion from a bad argument: its type parameter names the generator type and its bits field gives the span the draw needed. It carries no generator value, because holding one would slow every draw. You still hold the generator you passed in. Derive a fresh key with splitrng or subrng and restart the work there, rather than reusing the exhausted position.

Foreign Random methods reaching StatefulRNG hooks can write partially before exhaustion. Backend and resource failures do not carry the same atomicity guarantee.

Compiled Reactant carriers omit exhaustion checks. The caller must stay within capacity.