Decent talk by Roth Michaels, ADC 2022 Fast, High-quality Pseudo-random Numbers for Audio Developers. There is some example code at the end too.
I like that it is pretty straightforward to implement.
The authors recommend xoshiro128+ for floating point generation. There is a reference implementation here.
static inline uint32_t rotl(const uint32_t x, int k) {
return (x << k) | (x >> (32 - k));
}
static uint32_t s[4];
uint32_t next(void) {
const uint32_t result = s[0] + s[3];
const uint32_t t = s[1] << 9;
s[2] ^= s[0];
s[3] ^= s[1];
s[1] ^= s[2];
s[0] ^= s[3];
s[2] ^= t;
s[3] = rotl(s[3], 11);
return result;
}
The conversion of uint64_t to a float (0 to 1) is a bit of a fiddle to get your head around, but it is neat to understand.
/**
* 1. Extract 32bits
* 2. Drop the 8 bit mantissa
* 3. Mutliply by float32 precision
*/
//|------ 1 -----|-- 2 --|---- 3 ----|
((uint64_t >> 32u) >> 8) * 0x1.0p-24f;
The seed should be initialised using SplitMix64. Again very straightforward to implement.
static uint64_t x; /* The state can be seeded with any value. */
uint64_t next() {
uint64_t z = (x += 0x9e3779b97f4a7c15);
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
return z ^ (z >> 31);
}
That just leaves the SplitMix64 state to be initialised. I use std::random_device to generate two uint32_t, and then bit shift to create a single uint64_t.
std::random_device rdev;
uint64_t x = rdev();
// Fill the upper 32 bits
x <<= 32;
// Fill the lower 32 bits
x |= rdev();
PCG could be used as an alternative to xoshiro. I didn't look into it much as implementing xoshiro was very straightforward and it seems good enough for my needs.