Add a pseudo-random number generator in C

This commit is contained in:
rakiru 2016-01-23 21:43:38 +00:00
parent 5eed2ddc47
commit f87fe1cb99
6 changed files with 199 additions and 1 deletions

View File

@ -48,9 +48,10 @@ set(MAIN_FILES
src/model.c src/model.c
src/network.c src/network.c
src/path.c src/path.c
src/png.c
src/random.c
src/vecmath.c src/vecmath.c
src/wav.c src/wav.c
src/png.c
) )
set(GL_FILES set(GL_FILES

View File

@ -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" 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, ...) @ str = common.net_pack(fmt, ...) @
packs data into a string packs data into a string

View File

@ -449,6 +449,11 @@ enum
BT_MAX BT_MAX
}; };
typedef struct prng {
uint64_t state;
uint64_t stream;
} prng_t;
typedef struct packet packet_t; typedef struct packet packet_t;
struct packet 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); img_t *img_load_png(const char *fname, lua_State *L);
void img_write_png(const char *fname, img_t *img); 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 // vecmath.c
vec4f_t mtx_apply_vec(matrix_t *mtx, vec4f_t *vec); vec4f_t mtx_apply_vec(matrix_t *mtx, vec4f_t *vec);
void mtx_identity(matrix_t *mtx); void mtx_identity(matrix_t *mtx);

View File

@ -193,6 +193,7 @@ int icelua_fn_client_mk_set_title(lua_State *L)
#include "lua_mus.h" #include "lua_mus.h"
#include "lua_model.h" #include "lua_model.h"
#include "lua_net.h" #include "lua_net.h"
#include "lua_random.h"
#include "lua_tcp.h" #include "lua_tcp.h"
#include "lua_udp.h" #include "lua_udp.h"
#include "lua_util.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_unpack_array, "net_unpack_array"},
{icelua_fn_common_net_send, "net_send"}, {icelua_fn_common_net_send, "net_send"},
{icelua_fn_common_net_recv, "net_recv"}, {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_connect, "tcp_connect"},
{icelua_fn_common_tcp_send, "tcp_send"}, {icelua_fn_common_tcp_send, "tcp_send"},
{icelua_fn_common_tcp_recv, "tcp_recv"}, {icelua_fn_common_tcp_recv, "tcp_recv"},

104
src/lua_random.h Normal file
View 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
View 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);
}