Add prng_jump to allow taking arbitrary steps through the PRNG

This commit is contained in:
rakiru 2016-01-25 22:31:39 +00:00
parent f87fe1cb99
commit 4c20dea060
4 changed files with 44 additions and 2 deletions

View File

@ -738,6 +738,8 @@ common.prng_new(seed, stream) @
- random(max)
- random(min, max)
- returns a random number between min and max, defaulting to values of 0 and 1
- jump(step)
- jump ahead by step amount, can be negative
str = common.net_pack(fmt, ...) @

View File

@ -698,6 +698,7 @@ void prng_seed(prng_t *rng, uint64_t seed, uint64_t stream);
uint32_t prng_random(prng_t *rng);
double prng_random_double(prng_t *rng);
double prng_random_double_range(prng_t *rng, double minimum, double maximum);
void prng_jump(prng_t *rng, uint64_t step);
// vecmath.c
vec4f_t mtx_apply_vec(matrix_t *mtx, vec4f_t *vec);

View File

@ -21,6 +21,12 @@
// taken, but without a way to take arbitrary steps, this is pretty useless.
// It would also require storing more state, but it's only 128 bits at the
// moment, which is already pretty small for a PRNG.
// * We have jump now, but if we allow seeding with strings, then we could get
// initial seeds that can't fit in Lua anyway.
// * We could return a hex string or something, but that would require us to
// accept hex strings only. How would we deal with non-hex strings? Separate
// function for hex strings? get_state/set_state, but allow arbitrary strings
// (and hash them somehow) in seed?
// * We could add a clone function that creates a new one with the same state.
// Useless for networking though.
@ -67,6 +73,15 @@ int icelua_fn_cl_prng_random(lua_State *L)
return 1;
}
int icelua_fn_cl_prng_jump(lua_State *L)
{
int top = icelua_assert_stack(L, 1, 1);
prng_t *rng = lua_touserdata(L, lua_upvalueindex(1));
uint64_t step = (uint64_t)lua_tonumber(L, 1);
prng_jump(rng, step);
return 0;
}
int icelua_fn_common_prng_new(lua_State *L)
{
int top = icelua_assert_stack(L, 0, 2);
@ -96,6 +111,11 @@ int icelua_fn_common_prng_new(lua_State *L)
lua_pushcclosure(L, &icelua_fn_cl_prng_random, 1); // Create closure, 1 upvalue (the RNG state)
lua_settable(L, -4); // Insert closure into table
lua_pushstring(L, "jump"); // Function name
lua_pushvalue(L, -2); // Duplicate RNG reference to top of stack
lua_pushcclosure(L, &icelua_fn_cl_prng_jump, 1); // Create closure, 1 upvalue (the RNG state)
lua_settable(L, -4); // Insert closure into table
// Pop the RNG state off the stack.
lua_pop(L, 1);

View File

@ -20,8 +20,9 @@
// Basic implementation of the PCG psuedorandom number generator.
// Specifically, PCG-XSH-RR, as that's what the paper recommends for general use.
// This may diverge from the official implementations, but I wanted to give credit.
// TODO: We could drop the stream part and use a set value. We have no real use
// for it, and it complicates the API. It does give us more possible sequences though.
// TODO: We could drop the stream part and use a set value. It's useful for seeding
// a new PRNG from an existing one's output, but if people just want random seeds,
// we can seed from time.
// On the other hand, using a set value does allow us to ensure that a relatively
// good value is chosen (co-primes, etc.).
@ -64,3 +65,21 @@ double prng_random_double_range(prng_t *rng, double minimum, double maximum) {
double d = (double)random / UINT32_MAX;
return minimum + d * (maximum - minimum);
}
// Magical maths thanks to Forrest B. Brown, "Random number Generation with Arbitrary Strides"
void prng_jump(prng_t *rng, uint64_t step) {
uint64_t m_result = 1;
uint64_t c_result = 0;
uint64_t stream = rng->stream;
uint64_t multiplier = PRNG_MULTIPLIER;
while (step > 0) {
if (step & 1) {
m_result *= multiplier;
c_result = c_result * multiplier + stream;
}
stream = (multiplier + 1) * stream;
multiplier *= multiplier;
step >>= 1;
}
rng->state = m_result * rng->state + c_result;
}