From ae7814fe7cdd2bee049144942a520f422a1cb9b9 Mon Sep 17 00:00:00 2001 From: jun <83899451+zeichensystem@users.noreply.github.com> Date: Sat, 29 Mar 2025 15:28:03 +0100 Subject: [PATCH] Comment guf_rand --- src/guf_common.h | 6 +- src/guf_rand.h | 211 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 156 insertions(+), 61 deletions(-) diff --git a/src/guf_common.h b/src/guf_common.h index 30c9c30..ac5765e 100644 --- a/src/guf_common.h +++ b/src/guf_common.h @@ -10,8 +10,9 @@ #include #include -#define GUF_PLATFORM_LITTLE_ENDIAN -// #define GUF_PLATFORM_BIG_ENDIAN +#ifndef GUF_PLATFORM_BIG_ENDIAN + #define GUF_PLATFORM_LITTLE_ENDIAN +#endif #if SIZE_MAX == UINT64_MAX #define GUF_PLATFORM_BITS 64 @@ -37,7 +38,6 @@ #else #error "libguf only supports C99 and above" #endif - #if __STDC_VERSION__ >= 201112L #define GUF_STDC_AT_LEAST_C11 #endif diff --git a/src/guf_rand.h b/src/guf_rand.h index 2988b06..0c16313 100644 --- a/src/guf_rand.h +++ b/src/guf_rand.h @@ -10,24 +10,39 @@ #ifndef GUF_RAND_H #define GUF_RAND_H - #include "guf_common.h" +/* + - guf_rand32 functions use the xoshiro128** 1.1 generator [1] (rng-state of 4 32-bit integers, i.e. 128 bits) + - guf_rand64 functions use the xoshiro256** 1.0 generator [2] (rng-state of 4 64-bit integers, i.e. 256 bits) + - guf_rand functions use either guf_rand32 or guf_rand64 depending on GUF_RAND_32_BIT (which is set globally in guf_common.h depending on the platform's word-size) + + [1] xoshiro128** 1.1 (public domain) written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + cf. https://prng.di.unimi.it/xoshiro128starstar.c (last-retrieved 2025-02-11) + + [2] xoshiro256** 1.0 (public domain) written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + cf. https://prng.di.unimi.it/xoshiro256starstar.c (last-retrieved 2025-02-11) +*/ + +// State for xoshiro128** 1.1 typedef struct guf_rand32_state { - uint32_t s[4]; + uint32_t s[4]; // Must not be all zero. } guf_rand32_state; +// State for xoshiro256** 1.0 #ifdef UINT64_MAX typedef struct guf_rand64_state { - uint64_t s[4]; + uint64_t s[4]; // Must not be all zero. } guf_rand64_state; #endif #ifdef GUF_RAND_32_BIT + // Use guf_rand32_state (i.e. xoshiro128** 1.1) as default. #define GUF_RAND_MAX UINT32_MAX typedef guf_rand32_state guf_randstate; typedef uint32_t guf_rand_seed_t; #else + // Use guf_rand64_state (i.e. xoshiro256** 1.0) as default. #ifndef UINT64_MAX #error "guf_rand.h: Platform does not support uint64_t (define GUF_RAND_32_BIT to fix)" #endif @@ -36,100 +51,174 @@ typedef struct guf_rand32_state { typedef uint64_t guf_rand_seed_t; #endif -#ifdef UINT64_MAX - GUF_RAND_KWRDS uint64_t guf_rand_splitmix64(uint64_t *state); -#endif -GUF_RAND_KWRDS uint32_t guf_rand_splitmix32(uint32_t *state); - +/* + - guf_randstate_init(state, seed) -> void + Initialise the rng-state from a single 64-bit (or 32-bit) seed. + The seed is scrambled by guf_rand_splitmix64/32 internally. + Non-permissible states, i.e. states where all four state-integers turn out to be zero, + will be automatically corrected, which means all seeds passed to guf_randstate_init are permissible. + (If you want to initialise the guf_randstate struct manually, you have to ensure yourself the four state-integers aren't all zero.) +*/ GUF_RAND_KWRDS void guf_randstate_init(guf_randstate *state, guf_rand_seed_t seed); #ifdef UINT64_MAX GUF_RAND_KWRDS void guf_rand64_state_init(guf_rand64_state *state, uint64_t seed); #endif GUF_RAND_KWRDS void guf_rand32_state_init(guf_rand32_state *state, uint32_t seed); -GUF_RAND_KWRDS void guf_randstate_jump(guf_randstate *state); // Advance the state; equivalent to 2^128 (or 2^64) calls to guf_rand(state) +/* + - guf_randstate_jump(state) + -> void; advance the rng-state as if 2^128 (or 2^64 for rand32_state) calls to guf_rand_u32 had occured. + Can be used to generate 2^128 (or 2^64 for rand32_state) non-overlapping subsequences for parallel computations. +*/ +GUF_RAND_KWRDS void guf_randstate_jump(guf_randstate *state); // Equivalent to 2^128 (or 2^64) calls to guf_rand_u32 #ifdef UINT64_MAX - GUF_RAND_KWRDS void guf_rand64_state_jump(guf_rand64_state *state); // equivalent to 2^128 calls to guf_rand64_u64(state) + GUF_RAND_KWRDS void guf_rand64_state_jump(guf_rand64_state *state); // Equivalent to 2^128 calls to guf_rand64_u64 #endif -GUF_RAND_KWRDS void guf_rand32_state_jump(guf_rand32_state *state); // equivalent to 2^64 calls to guf_rand32_u32(state) +GUF_RAND_KWRDS void guf_rand32_state_jump(guf_rand32_state *state); // Equivalent to 2^64 calls to guf_rand32_u32 -// Uniform distributions: -GUF_RAND_KWRDS uint32_t guf_rand_u32(guf_randstate *state); // [0, UINT32_MAX] +// Uniform distributions in default ranges: + +/* + - guf_rand_splitmix64(state) -> uint64_t in range [0, UINT64_MAX] + (Very simple rng with only 64-bits of state; used for "scrambling" 64-bit seeds in guf_randstate_init.) + - guf_rand_splitmix32(state) -> uint32_t in range [0, UINT32_MAX] + (Very simple rng with only 32-bits of state; used for "scrambling" 32-bit seeds in guf_randstate_init.) +*/ +#ifdef UINT64_MAX + GUF_RAND_KWRDS uint64_t guf_rand_splitmix64(uint64_t *state); +#endif +GUF_RAND_KWRDS uint32_t guf_rand_splitmix32(uint32_t *state); + +/* + - guf_rand_u32(state) -> uint32_t in range [0, UINT32_MAX] +*/ +GUF_RAND_KWRDS uint32_t guf_rand_u32(guf_randstate *state); #ifdef UINT64_MAX - // NOTE: slow on 32-bit platforms. GUF_RAND_KWRDS uint32_t guf_rand64_u32(guf_rand64_state *state); #endif GUF_RAND_KWRDS uint32_t guf_rand32_u32(guf_rand32_state *state); +/* + - guf_rand_u64(state) -> uint64_t (or uint_least64_t) in range [0, UINT64_MAX] + NOTE: May be slow on 32-bit platforms. + NOTE: If uint64_t is not available (optional according to the standards), use uint_least64_t (always available in C99 and above). +*/ #ifdef UINT64_MAX - // NOTE: Slow on 32-bit platforms. - GUF_RAND_KWRDS uint64_t guf_rand_u64(guf_randstate *state); // [0, UINT64_MAX] + GUF_RAND_KWRDS uint64_t guf_rand_u64(guf_randstate *state); GUF_RAND_KWRDS uint64_t guf_rand32_u64(guf_rand32_state *state); GUF_RAND_KWRDS uint64_t guf_rand64_u64(guf_rand64_state *state); #else - GUF_RAND_KWRDS uint_least64_t guf_rand_u64(guf_randstate *state) // NOTE: Slow on 32-bit platforms. - GUF_RAND_KWRDS uint_least64_t guf_rand32_u64(guf_rand32_state *state); // NOTE: Slow on 32-bit platforms. + GUF_RAND_KWRDS uint_least64_t guf_rand_u64(guf_randstate *state) + GUF_RAND_KWRDS uint_least64_t guf_rand32_u64(guf_rand32_state *state); #endif -GUF_RAND_KWRDS double guf_rand_f64(guf_randstate *state); // [0.0, 1.0) (NOTE: slow on 32-bit platforms.) +/* + - guf_rand_f64(state) -> double in range [0.0, 1.0) + NOTE: May be slow on 32-bit platforms (as it calls guf_rand_u64) +*/ +GUF_RAND_KWRDS double guf_rand_f64(guf_randstate *state); #ifdef UINT64_MAX - // NOTE: slow on 32-bit platforms. GUF_RAND_KWRDS double guf_rand64_f64(guf_rand64_state *state); #endif -GUF_RAND_KWRDS double guf_rand32_f64(guf_rand32_state *state); // NOTE: slow on 32-bit platforms. +GUF_RAND_KWRDS double guf_rand32_f64(guf_rand32_state *state); -GUF_RAND_KWRDS float guf_rand_f32(guf_randstate *state); // [0.f, 1.f) +/* + - guf_rand_f32(state) -> float in range [0.f, 1.f) +*/ +GUF_RAND_KWRDS float guf_rand_f32(guf_randstate *state); #ifdef UINT64_MAX - // NOTE: slow on 32-bit platforms. GUF_RAND_KWRDS float guf_rand64_f32(guf_rand64_state *state); #endif GUF_RAND_KWRDS float guf_rand32_f32(guf_rand32_state *state); -// return true with a probability of p, false with a probability of (1 - p) -GUF_RAND_KWRDS bool guf_rand_bernoulli_trial_f32(guf_randstate *state, float p); -GUF_RAND_KWRDS bool guf_rand_bernoulli_trial_f64(guf_randstate *state, double p); // NOTE: slow on 32-bit platforms. -GUF_RAND_KWRDS bool guf_rand_flip(guf_randstate *state); // Fair coin flip (bernoulli trial with p == 0.5) + +// Uniform distributions in custom ranges: + +/* + - guf_randrange_f32(state, min, end) -> float in range [min, end) (contrary to the integer equivalents, end is *not* inclusive) + - guf_randrange_f64(state, min, end) -> double in range [min, end) (contrary to the integer equivalents, end is *not* inclusive) + NOTE: f64 versions may be slow on 32-bit platforms. +*/ +GUF_RAND_KWRDS float guf_randrange_f32(guf_randstate *state, float min, float end); +GUF_RAND_KWRDS double guf_randrange_f64(guf_randstate *state, double min, double end); +#ifdef UINT64_MAX + GUF_RAND_KWRDS float guf_rand64_range_f32(guf_rand64_state *state, float min, float end); + GUF_RAND_KWRDS double guf_rand64_range_f64(guf_rand64_state *state, double min, double end); +#endif +GUF_RAND_KWRDS float guf_rand32_range_f32(guf_rand32_state *state, float min, float end); +GUF_RAND_KWRDS double guf_rand32_range_f64(guf_rand32_state *state, double min, double end); + +/* + - guf_randrange_i32(state, min, max) -> int32_t in range [min, max] (contrary to the float equivalents, max *is* inclusive) + - guf_randrange_u32(state, min, max) -> uint32_t in range [min, max] (contrary to the float equivalents, max *is* inclusive) + NOTE: guf_randrange_u32 may be slow on 32-bit platforms (as it calls guf_rand_f64). + This does not apply to guf_randrange_i32 (as it doesn't call guf_rand_f64). +*/ +GUF_RAND_KWRDS int32_t guf_randrange_i32(guf_randstate *state, int32_t min, int32_t max); +GUF_RAND_KWRDS uint32_t guf_randrange_u32(guf_randstate *state, uint32_t min, uint32_t max); // NOTE: may be slow on 32-bit platforms. +#ifdef UINT64_MAX + GUF_RAND_KWRDS int32_t guf_rand64_range_i32(guf_rand64_state *state, int32_t min, int32_t max); + GUF_RAND_KWRDS uint32_t guf_rand64_range_u32(guf_rand64_state *state, uint32_t min, uint32_t max); +#endif +GUF_RAND_KWRDS int32_t guf_rand32_range_i32(guf_rand32_state *state, int32_t min, int32_t max); +GUF_RAND_KWRDS uint32_t guf_rand32_range_u32(guf_rand32_state *state, uint32_t min, uint32_t max); // NOTE: may be slow on 32-bit platforms. + +/* + - guf_randrange_i64(state, min, max) -> int64_t in range [min, max] (contrary to the float equivalents, max *is* inclusive) + NOTE: The Generic version is only available if GUF_RAND_32_BIT is undefined and the platform supports uint64_t. + (The specific guf_rand64_range_i64 version is available as long as the platform supports uint64_t.) +*/ +#if defined(UINT64_MAX) && !defined(GUF_RAND_32_BIT) + GUF_RAND_KWRDS int64_t guf_randrange_i64(guf_randstate *state, int64_t min, int64_t max); +#endif +#ifdef UINT64_MAX + GUF_RAND_KWRDS int64_t guf_rand64_range_i64(guf_rand64_state *state, int64_t min, int64_t max); +#endif + +// Bernoulli-trials: + +/* + - guf_rand_bernoulli_trial(state, p) -> return true with a probability of p, false with a probability of (1 - p) + NOTE: p will be clamped to be in range [0.0, 1.0] + NOTE: The f64 versions may be slow on 32-bit platforms. + - guf_rand_flip(state) -> return true with a probability of 50 %, i.e. fair coin flip (bernoulli trial with p == 0.5) +*/ +GUF_RAND_KWRDS bool guf_rand_bernoulli_trial_f32(guf_randstate *state, float p); +GUF_RAND_KWRDS bool guf_rand_bernoulli_trial_f64(guf_randstate *state, double p); +GUF_RAND_KWRDS bool guf_rand_flip(guf_randstate *state); #ifdef UINT64_MAX - // NOTE: slow on 32-bit platforms. GUF_RAND_KWRDS bool guf_rand64_bernoulli_trial_f32(guf_rand64_state *state, float p); GUF_RAND_KWRDS bool guf_rand64_bernoulli_trial_f64(guf_rand64_state *state, double p); GUF_RAND_KWRDS bool guf_rand64_flip(guf_rand64_state *state); #endif GUF_RAND_KWRDS bool guf_rand32_bernoulli_trial_f32(guf_rand32_state *state, float p); -GUF_RAND_KWRDS bool guf_rand32_bernoulli_trial_f64(guf_rand32_state *state, double p); // NOTE: slow on 32-bit platforms. +GUF_RAND_KWRDS bool guf_rand32_bernoulli_trial_f64(guf_rand32_state *state, double p); GUF_RAND_KWRDS bool guf_rand32_flip(guf_rand32_state *state); -GUF_RAND_KWRDS double guf_randrange_f64(guf_randstate *state, double min, double end); // [min, end) (NOTE: slow on 32-bit platforms.) -GUF_RAND_KWRDS float guf_randrange_f32(guf_randstate *state, float min, float end); // [min, end) -#ifdef UINT64_MAX - // (NOTE: slow on 32-bit platforms.) - GUF_RAND_KWRDS double guf_rand64_range_f64(guf_rand64_state *state, double min, double end); - GUF_RAND_KWRDS float guf_rand64_range_f32(guf_rand64_state *state, float min, float end); -#endif -GUF_RAND_KWRDS double guf_rand32_range_f64(guf_rand32_state *state, double min, double end); // (NOTE: slow on 32-bit platforms.) -GUF_RAND_KWRDS float guf_rand32_range_f32(guf_rand32_state *state, float min, float end); +// Normal distributions: -GUF_RAND_KWRDS int32_t guf_randrange_i32(guf_randstate *state, int32_t min, int32_t max); // [min, max] -GUF_RAND_KWRDS uint32_t guf_randrange_u32(guf_randstate *state, uint32_t min, uint32_t max); // [min, max] (NOTE: slow on 32-bit platforms.) -#ifdef UINT64_MAX - // (NOTE: slow on 32-bit platforms.) - GUF_RAND_KWRDS int32_t guf_rand64_range_i32(guf_rand64_state *state, int32_t min, int32_t max); - GUF_RAND_KWRDS uint32_t guf_rand64_range_u32(guf_rand64_state *state, uint32_t min, uint32_t max); -#endif -GUF_RAND_KWRDS int32_t guf_rand32_range_i32(guf_rand32_state *state, int32_t min, int32_t max); -GUF_RAND_KWRDS uint32_t guf_rand32_range_u32(guf_rand32_state *state, uint32_t min, uint32_t max); // (NOTE: slow on 32-bit platforms.) - -#ifdef UINT64_MAX - // (NOTE: slow on 32-bit platforms.) - GUF_RAND_KWRDS int64_t guf_randrange_i64(guf_randstate *state, int64_t min, int64_t max); // [min, max] (NOTE: slow on 32-bit platforms.) -#endif - -// normal distributions +/* + - guf_rand_normal_sample_f32/f64(state, mean, std_dev, result, n) + -> void; put n float/double samples following the given normal-distribution into result (result is allocated by the caller and must have enough space to hold at least n samples) + - guf_rand_normal_sample_one_f32/f64(state, mean, std_dev) + -> return one float/double sample following the given normal-distribution + - NOTE: the f64 versions may be slow on 32-bit platforms. +*/ GUF_RAND_KWRDS void guf_rand_normal_sample_f64(guf_randstate *state, double mean, double std_dev, double *result, ptrdiff_t n); GUF_RAND_KWRDS void guf_rand_normal_sample_f32(guf_randstate *state, float mean, float std_dev, float *result, ptrdiff_t n); GUF_RAND_KWRDS double guf_rand_normal_sample_one_f64(guf_randstate *state, double mean, double std_dev); GUF_RAND_KWRDS float guf_rand_normal_sample_one_f32(guf_randstate *state, float mean, float std_dev); +#ifdef UINT64_MAX + GUF_RAND_KWRDS void guf_rand64_normal_sample_f32(guf_rand64_state *state, float mean, float std_dev, float *result, ptrdiff_t n); + GUF_RAND_KWRDS void guf_rand64_normal_sample_f64(guf_rand64_state *state, double mean, double std_dev, double *result, ptrdiff_t n); + GUF_RAND_KWRDS float guf_rand64_normal_sample_one_f32(guf_rand64_state *state, float mean, float std_dev); + GUF_RAND_KWRDS double guf_rand64_normal_sample_one_f64(guf_rand64_state *state, double mean, double std_dev); +#endif +GUF_RAND_KWRDS void guf_rand32_normal_sample_f32(guf_rand32_state *state, float mean, float std_dev, float *result, ptrdiff_t n); +GUF_RAND_KWRDS void guf_rand32_normal_sample_f64(guf_rand32_state *state, double mean, double std_dev, double *result, ptrdiff_t n); +GUF_RAND_KWRDS float guf_rand32_normal_sample_one_f32(guf_rand32_state *state, float mean, float std_dev); +GUF_RAND_KWRDS double guf_rand32_normal_sample_one_f64(guf_rand32_state *state, double mean, double std_dev); #endif @@ -599,6 +688,9 @@ GUF_RAND_KWRDS int32_t guf_rand32_range_i32(guf_rand32_state *state, int32_t min return -1; } /* + We don't use the same approach as in guf_rand64_range_i32 because a 32-bit float only has a 24-bit mantissa + (and using double like in guf_rand64_range_i32 would require 64-bit arithmetic due to guf_rand32_f64). + cf. https://c-faq.com/lib/randrange.html (last-retrieved 2025-02-11) https://stackoverflow.com/a/6852396 (last-retrieved 2025-02-11) */ @@ -637,12 +729,17 @@ GUF_RAND_KWRDS int32_t guf_randrange_i32(guf_randstate *state, int32_t min, int3 GUF_RAND_KWRDS uint32_t guf_rand32_range_u32(guf_rand32_state *state, uint32_t min, uint32_t max) { + /* + The method used in guf_rand32_range_i32 above (which uses only 32-bit integer arithmetic) could overflow here, + so we use the floating-point method since we have to use 64-bit arithmetic anyways. + */ + GUF_ASSERT_RELEASE(max >= min); if (min == max) { return min; } const double delta = (double)max - (double)min; - const double result = floor(guf_rand32_f64(state) * (delta + 1.0) + min); // TODO: slow for 32-bit platforms... + const double result = floor(guf_rand32_f64(state) * (delta + 1.0) + min); // NOTE: guf_rand32_f64 is slow for 32-bit platforms... GUF_ASSERT(result >= min && result <= max); return (uint32_t)result; } @@ -686,7 +783,7 @@ GUF_RAND_KWRDS uint32_t guf_randrange_u32(guf_randstate *state, uint32_t min, ui return -1; } /* - We should not use the same approach as in guf_randrange_i32 because (max - min) might be close to 2^63 - 1 + We should not use the same approach as in guf_rand64_range_i32 because (max - min) might be close to 2^63 - 1 cf. https://c-faq.com/lib/randrange.html (last-retrieved 2025-02-11) https://stackoverflow.com/a/6852396 (last-retrieved 2025-02-11) @@ -723,7 +820,6 @@ GUF_RAND_KWRDS uint32_t guf_randrange_u32(guf_randstate *state, uint32_t min, ui } #endif - // Box-Müller-transform transcribed from wikipedia, cf. https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform (last-retrieved 2025-02-12) GUF_RAND_KWRDS void guf_rand32_normal_sample_f64(guf_rand32_state *state, double mean, double std_dev, double *result, ptrdiff_t n) { @@ -772,7 +868,6 @@ GUF_RAND_KWRDS void guf_rand32_normal_sample_f64(guf_rand32_state *state, double } #endif - // Box-Müller-transform transcribed from wikipedia, cf. https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform (last-retrieved 2025-02-12) GUF_RAND_KWRDS void guf_rand_normal_sample_f64(guf_randstate *state, double mean, double std_dev, double *result, ptrdiff_t n) {