Rng
A reproducible stream of uint64_t values. The caller owns the LfRng struct, so two simulations can keep two generators. Seeding is a pure function of the seed: it reads no clock and no device. There is no global generator, and the module does not allocate.
The generator is xoshiro256** 1.0 (Blackman and Vigna), seeded with SplitMix64. jump and long_jump are not part of this version. There is no lf_rng_f64. Concurrent use is not supported.
Usage#
Seed a stack LfRng, then draw a full word or a uniform value in [0, n).
#include <stdio.h>
#include "lfrng.h"
int main(void) {
LfRng rng;
uint64_t value;
if (lf_rng_seed(&rng, 1) != 0) {
return 1;
}
if (lf_rng_next_u64(&rng, &value) != 0) {
return 1;
}
if (lf_rng_next_range(&rng, 6, &value) != 0) {
return 1;
}
printf("%llu\n", (unsigned long long)value);
return 0;
}
lf_rng_seed, lf_rng_next_u64, and lf_rng_next_range return 0 on success and -1 on failure. On failure the handle is unchanged. When out is non-NULL, *out is also unchanged. A bitwise copy of LfRng copies the position in the stream. s[0] through s[3] are the expanded xoshiro256** state, not the original uint64_t seed.
Structs#
LfRng#
typedef struct {
uint64_t s[4];
} LfRng;
The four words are public. Write them through lf_rng_seed, or copy a whole struct to snapshot the stream. The all-zero state is not a usable stream.
Functions#
lf_rng_seed#
Runs SplitMix64 four times, starting from seed, and stores the results in s[0], s[1], s[2], and s[3]. Returns 0 on success. Returns -1 if rng is NULL or if the expanded state is all zeros. On failure the handle is unchanged.
int lf_rng_seed(LfRng *rng, uint64_t seed);lf_rng_next_u64#
Advances the xoshiro256** state and stores the next uint64_t in *out. Returns 0 on success. Returns -1 if rng or out is NULL, or if the state is all zeros. The all-zero state is rejected before the update, so a failed call leaves the state and *out unchanged.
int lf_rng_next_u64(LfRng *rng, uint64_t *out);lf_rng_next_range#
Stores a uniform value in [0, n) in *out. Returns 0 on success. Returns -1 if rng or out is NULL, if n is 0, or if the state is all zeros. Those checks happen before the first draw, so a failed call does not advance the state and leaves *out unchanged.
The value is drawn by rejection so it is uniform for every n. The rejected zone is 2^64 % n, computed as (-n) % n in uint64_t arithmetic. That zone is zero when n is a power of two, including n == 1, and the call then consumes exactly one lf_rng_next_u64. This is not a bare next % n for arbitrary n.
int lf_rng_next_range(LfRng *rng, uint64_t n, uint64_t *out);