Skip to content

Commit

Permalink
Add a pseudo-random number generator in C
Browse files Browse the repository at this point in the history
  • Loading branch information
rakiru committed Jan 25, 2016
1 parent 5eed2dd commit f87fe1c
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions docs/modding_lua.txt
Expand Up @@ -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

Expand Down
11 changes: 11 additions & 0 deletions include/common.h
Expand Up @@ -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
{
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/lua.c
Expand Up @@ -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"
Expand Down Expand Up @@ -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"},
Expand Down
104 changes: 104 additions & 0 deletions src/lua_random.h
@@ -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 changes: 66 additions & 0 deletions src/random.c
@@ -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);
}

0 comments on commit f87fe1c

Please sign in to comment.