Add a pseudo-random number generator in C
This commit is contained in:
parent
5eed2ddc47
commit
f87fe1cb99
@ -48,9 +48,10 @@ set(MAIN_FILES
|
||||
src/model.c
|
||||
src/network.c
|
||||
src/path.c
|
||||
src/png.c
|
||||
src/random.c
|
||||
src/vecmath.c
|
||||
src/wav.c
|
||||
src/png.c
|
||||
)
|
||||
|
||||
set(GL_FILES
|
||||
|
@ -726,6 +726,20 @@ common.json_write(fname, value) @
|
||||
|
||||
to tell the difference between tables and arrays it checks for the existence of the key "1"
|
||||
|
||||
common.prng_new(seed, stream) @
|
||||
creates a new pseudo-random number generator instance
|
||||
|
||||
seed and stream are both optional
|
||||
|
||||
returns a table of the following functions:
|
||||
- seed(seed, stream)
|
||||
- allows reseeding of the RNG, both arguments are optional
|
||||
- random()
|
||||
- random(max)
|
||||
- random(min, max)
|
||||
- returns a random number between min and max, defaulting to values of 0 and 1
|
||||
|
||||
|
||||
str = common.net_pack(fmt, ...) @
|
||||
packs data into a string
|
||||
|
||||
|
@ -449,6 +449,11 @@ enum
|
||||
BT_MAX
|
||||
};
|
||||
|
||||
typedef struct prng {
|
||||
uint64_t state;
|
||||
uint64_t stream;
|
||||
} prng_t;
|
||||
|
||||
typedef struct packet packet_t;
|
||||
struct packet
|
||||
{
|
||||
@ -688,6 +693,12 @@ img_t *img_parse_png(int len, const char *data, lua_State *L);
|
||||
img_t *img_load_png(const char *fname, lua_State *L);
|
||||
void img_write_png(const char *fname, img_t *img);
|
||||
|
||||
// random.c
|
||||
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);
|
||||
|
||||
// vecmath.c
|
||||
vec4f_t mtx_apply_vec(matrix_t *mtx, vec4f_t *vec);
|
||||
void mtx_identity(matrix_t *mtx);
|
||||
|
@ -193,6 +193,7 @@ int icelua_fn_client_mk_set_title(lua_State *L)
|
||||
#include "lua_mus.h"
|
||||
#include "lua_model.h"
|
||||
#include "lua_net.h"
|
||||
#include "lua_random.h"
|
||||
#include "lua_tcp.h"
|
||||
#include "lua_udp.h"
|
||||
#include "lua_util.h"
|
||||
@ -319,6 +320,7 @@ struct icelua_entry icelua_common[] = {
|
||||
{icelua_fn_common_net_unpack_array, "net_unpack_array"},
|
||||
{icelua_fn_common_net_send, "net_send"},
|
||||
{icelua_fn_common_net_recv, "net_recv"},
|
||||
{icelua_fn_common_prng_new, "prng_new"},
|
||||
{icelua_fn_common_tcp_connect, "tcp_connect"},
|
||||
{icelua_fn_common_tcp_send, "tcp_send"},
|
||||
{icelua_fn_common_tcp_recv, "tcp_recv"},
|
||||
|
104
src/lua_random.h
Normal file
104
src/lua_random.h
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
This file is part of Iceball.
|
||||
|
||||
Iceball is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Iceball is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Iceball. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Notes:
|
||||
// * There's no `get_seed` function because state is stored in a uint64, which
|
||||
// we can't shove into a double. We could return the original seed and steps
|
||||
// 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 could add a clone function that creates a new one with the same state.
|
||||
// Useless for networking though.
|
||||
|
||||
int icelua_fn_cl_prng_seed(lua_State *L)
|
||||
{
|
||||
int top = icelua_assert_stack(L, 0, 2);
|
||||
uint64_t seed = 0; // TODO: Seed from time or something, if not given
|
||||
uint64_t stream = 0;
|
||||
if (top >= 1) {
|
||||
seed = (uint64_t)lua_tointeger(L, 1);
|
||||
if (top >= 2) {
|
||||
stream = (uint64_t)lua_tointeger(L, 2);
|
||||
}
|
||||
}
|
||||
prng_t *rng = lua_touserdata(L, lua_upvalueindex(1));
|
||||
prng_seed(rng, seed, stream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Simulates Lua's math.random
|
||||
// 0 args: [0-1]
|
||||
// 1 args: [0-max]
|
||||
// 2 args: [min-max]
|
||||
int icelua_fn_cl_prng_random(lua_State *L)
|
||||
{
|
||||
int top = icelua_assert_stack(L, 0, 2);
|
||||
prng_t *rng = lua_touserdata(L, lua_upvalueindex(1));
|
||||
double result;
|
||||
if (top == 0) {
|
||||
result = prng_random_double(rng);
|
||||
} else {
|
||||
double minimum;
|
||||
double maximum;
|
||||
if (top == 1) {
|
||||
minimum = 0;
|
||||
maximum = lua_tonumber(L, 1);
|
||||
} else {
|
||||
minimum = lua_tonumber(L, 1);
|
||||
maximum = lua_tonumber(L, 2);
|
||||
}
|
||||
result = prng_random_double_range(rng, minimum, maximum);
|
||||
}
|
||||
lua_pushnumber(L, result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int icelua_fn_common_prng_new(lua_State *L)
|
||||
{
|
||||
int top = icelua_assert_stack(L, 0, 2);
|
||||
uint64_t seed = 0; // TODO: Seed from time or something, if not given
|
||||
uint64_t stream = 0;
|
||||
if (top >= 1) {
|
||||
seed = (uint64_t)lua_tointeger(L, 1);
|
||||
if (top >= 2) {
|
||||
stream = (uint64_t)lua_tointeger(L, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// "this" table
|
||||
lua_createtable(L, 0, 4);
|
||||
|
||||
// PRNG state - uses upvalues, not visible to Lua, but hey, GC
|
||||
prng_t *rng = lua_newuserdata(L, sizeof(prng_t));
|
||||
prng_seed(rng, seed, stream);
|
||||
|
||||
lua_pushstring(L, "seed"); // Function name
|
||||
lua_pushvalue(L, -2); // Duplicate RNG reference to top of stack
|
||||
lua_pushcclosure(L, &icelua_fn_cl_prng_seed, 1); // Create closure, 1 upvalue (the RNG state)
|
||||
lua_settable(L, -4); // Insert closure into table
|
||||
|
||||
lua_pushstring(L, "random"); // Function name
|
||||
lua_pushvalue(L, -2); // Duplicate RNG reference to top of stack
|
||||
lua_pushcclosure(L, &icelua_fn_cl_prng_random, 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);
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
66
src/random.c
Normal file
66
src/random.c
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
This file is part of Iceball.
|
||||
|
||||
Iceball is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Iceball is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Iceball. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
// 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.
|
||||
// On the other hand, using a set value does allow us to ensure that a relatively
|
||||
// good value is chosen (co-primes, etc.).
|
||||
|
||||
#define PRNG_MULTIPLIER 6364136223846793005ULL
|
||||
|
||||
void prng_seed(prng_t *rng, uint64_t seed, uint64_t stream) {
|
||||
rng->state = 0;
|
||||
// This must be odd (although it will still work sub-optimally if even)
|
||||
rng->stream = (stream << 1) | 1;
|
||||
// Initialise the state
|
||||
prng_random(rng);
|
||||
rng->state += seed;
|
||||
// Properly initialise the state (diverge the streams)
|
||||
prng_random(rng);
|
||||
}
|
||||
|
||||
uint32_t prng_random(prng_t *rng) {
|
||||
uint64_t state = rng->state;
|
||||
|
||||
// Update stored state
|
||||
rng->state = state * PRNG_MULTIPLIER + rng->stream;
|
||||
|
||||
// Generate number
|
||||
// Top 5 bits specify rotation (for 32 bit result):
|
||||
// * 64 - 5 = 59
|
||||
// * 32 - 5 = 27
|
||||
// * (5 + 32) / 2 = 18
|
||||
uint32_t xor_shifted = (uint32_t)(((state >> 18) ^ state) >> 27);
|
||||
uint32_t rotation = (uint32_t)(state >> 59);
|
||||
return (xor_shifted >> rotation) | (xor_shifted << (-rotation & 31));
|
||||
}
|
||||
|
||||
double prng_random_double(prng_t *rng) {
|
||||
uint32_t random = prng_random(rng);
|
||||
return (double)random / UINT32_MAX;
|
||||
}
|
||||
|
||||
double prng_random_double_range(prng_t *rng, double minimum, double maximum) {
|
||||
uint32_t random = prng_random(rng);
|
||||
double d = (double)random / UINT32_MAX;
|
||||
return minimum + d * (maximum - minimum);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user