The important property of determinism is that if I set a seed, and then make an identical set of calls to the random API, the results will be identical every time, so that it's possible to write tests with predictable/repeatable results.

Hmmm… I like my stronger determinism definition more than this one:-)

I would work around that by deriving another 128 bit generator instead of splitmix 64 bit, but that is overkill.

Not really relevant now, but I'm pretty sure that's impossible to do.
You might try it as an interesting academic exercise, but I believe
it's a logical impossibility.

Hmmm… I was simply thinking of seeding a new pg_prng_state from the main pg_prng_state with some transformation, and then iterate over the second PRNG, pretty much like I did with splitmix, but with 128 bits so that your #states argument does not apply, i.e. something like:

 /* select in a range with bitmask rejection */
 uint64 pg_prng_u64_range(pg_prng_state *state, uint64 range)
 {
    /* always advance state once */
    uint64 next = xoroshiro128ss(state);
    uint64 val;

    if (range >= 2)
    {
        uint64 mask = mask_u64(range-1);

        val = next & mask;

        if (val >= range)
        {
            /* copy and update current prng state */
            pg_prng_state iterator = *state;

            iterator.s0 ^= next;
            iterator.s1 += UINT64CONST(0x9E3779B97f4A7C15);

            /* iterate till val in [0, range) */
            while ((val = xoroshiro128ss(&iterator) & mask) >= range)
                ;
        }
    }
    else
        val = 0;

    return val;
 }

The initial pseudo-random sequence is left to proceed, and a new PRNG is basically forked for iterating on the mask, if needed.

--
Fabien.

Reply via email to