Simple atomic pseudo-random number generation I like to use a single global PRNG in my c programs (similar to Javascript's Math.random) because it simplifies things and the unpredictable nature of a global is quite fitting for a PRNG. I recently started using multithreading more so I adapted my global PRNG by making it atomic.

Besides the obvious, it is meant to be atomic in such a way that two threads requesting a pseudo-random number simultaneously won't get the same number in the sequence of pseudo-random numbers. Using a CAS-loop seems to be the only way of accomplishing this, if there is a better way do let me know.

Primarily I'm looking for confirmation that it actually is atomic. Note that struct randomic isn't the same as randomic, the latter is a typedef of _Atomic struct randomic. Admittedly not the best naming ever.

The real magic happens in randomicNext but the whole thing is pretty small so I've included all of it. It can also be found on github.

//include only once
#ifndef RANDOMIC_H
#define RANDOMIC_H

//process configuration
#ifdef RANDOMIC_STATIC
    #define RANDOMIC_IMPLEMENTATION
    #define RADEF static
#else //RANDOMIC_EXTERN
    #define RADEF extern
#endif

//includes
#include &lt;stdatomic.h&gt;
#include &lt;stdint.h&gt;

//types
typedef _Atomic struct randomic {
    uint32_t a, b, c, d;
} randomic;

//function declarations
RADEF void randomicSeed(randomic*, uint32_t);
RADEF float randomicFloat(randomic*, float, float);
RADEF double randomicDouble(randomic*, double, double);
RADEF uint32_t randomicNext(randomic*);

//implementation section
#ifdef RANDOMIC_IMPLEMENTATION

//function declarations
static struct randomic randomicInternal(struct randomic);

//public functions
RADEF void randomicSeed (randomic* rdic, uint32_t seed) {
    struct randomic ctx;
    ctx.a = 0xf1ea5eed;
    ctx.b = ctx.c = ctx.d = seed;
    for (int i = 0; i &lt; 20; i++)
        ctx = randomicInternal(ctx);
    atomic_store(rdic, ctx);
}
RADEF float randomicFloat (randomic* rdic, float a, float b) {
    return a + (b-a)*((float)randomicNext(rdic)/(float)UINT32_MAX);
}
RADEF double randomicDouble (randomic* rdic, double a, double b) {
    return a + (b-a)*((double)randomicNext(rdic)/(double)UINT32_MAX);
}
RADEF uint32_t randomicNext (randomic* rdic) {
    struct randomic ctx = atomic_load(rdic), ntx;
    while (!atomic_compare_exchange_weak(rdic, &amp;ctx, (ntx = randomicInternal(ctx))));
    return ntx.d;
}

//internal functions
static struct randomic randomicInternal (struct randomic ctx) {
    uint32_t e = ctx.a - ((ctx.b &lt;&lt; 27)|(ctx.b &gt;&gt; 5));
    ctx.a = ctx.b ^ ((ctx.c &lt;&lt; 17)|(ctx.c &gt;&gt; 15));
    ctx.b = ctx.c + ctx.d;
    ctx.c = ctx.d + e;
    ctx.d = e + ctx.a;
    return ctx;
}

#endif //RANDOMIC_IMPLEMENTATION
#endif //RANDOMIC_H
Primarily I'm looking for confirmation that it actually is atomic. 


Yes, it appears atomic.



Other issues

Range and precision

randomicDouble() needs more functional explanation about  range and precision.  It seems to attempt an about linear distribution between [a...b]   (b included).

A range of [a...b)  (b excluded) is quite common.  So detailing the coding goal is important.

The code fails to provide a full precision random number.  Simple example: randomicDouble(1.0, 2.0) typically has about 253 different values in the [1.0...2.0) range and this code only supplies 232 different values.

With randomicDouble(), perhaps sometimes 2 calls to randomicNext() are needed.

To provide a linear distribution with randomicDouble(1.0, 4.0) is trickier given the change in absolute precision between  [1.0...2.0) and  [2.0...4.0). randomicDouble(0.0, 1.0) is even tricker.

Initialization

Code passes in only a 32-bit initialization whereas the state is 128-bit.  I'd expect at least a 64-bit initialization if not a full 128-bit one.



Mnior

20 is a magic number in for (int i = 0; i &lt; 20; i++) and deserves explanation.