Skip to content
Permalink
Browse files

Replace ASG with PCG

This patch replaces the alternating step generator with M.E. O'Neill's
implementation of PCG.

Why bother? To set a good example. Rolling your own RNG is a path to sorrow
even when you sort of know what you're doing. While ASG passes BigCrush it
does so with extraneous state. PCG is small, statistically well characterized
and will undergo more scrutiny than some one-off generator.

Why not use ChaCha? I don't want to use a crypto-PRNG without a guarantee that
it's been seeded correctly.
  • Loading branch information...
bhickey committed Oct 23, 2015
1 parent 56a2587 commit 415d666aa2f05d6fa4273bb1b88c99fd389f19a4
@@ -15,6 +15,7 @@ These include:
* MIT: json.cc/json.h, some .js files in webserver/static/scripts/contrib/
worley.{cc,h}
* zlib: webserver/static/scripts/contrib/inflate.js
* Apache v2: pcg.cc

While a majority of the tiles are in the Public Domain, there is no accurate
list. To err on the side of caution, please refer to Chris Hamons'
@@ -9,7 +9,6 @@ adjust.o \
areas.o \
arena.o \
artefact.o \
asg.o \
attack.o \
attitude-change.o \
beam.o \
@@ -177,6 +176,7 @@ ouch.o \
output.o \
package.o \
pattern.o \
pcg.o \
perlin.o \
place-info.o \
place.o \

This file was deleted.

Oops, something went wrong.

This file was deleted.

Oops, something went wrong.
@@ -15,7 +15,7 @@
#include "areas.h"
#include "artefact.h"
#include "art-enum.h"
#include "asg.h" // for make_name()'s use
#include "pcg.h" // for make_name()'s use
#include "branch.h"
#include "butcher.h"
#include "cio.h"
@@ -2842,8 +2842,8 @@ const size_t RCS_END = RCS_EM;
*/
string make_name(uint32_t seed, makename_type name_type)
{
uint32_t sarg[1] = { seed };
AsgKISS rng = AsgKISS(sarg, 1);
uint64_t sarg[1] = { static_cast<uint64_t>(seed) };
PcgRNG rng = PcgRNG(sarg, 1);

string name;

@@ -0,0 +1,81 @@
/* This class implements a 128-bit pseudo-random number generator.
* Despite a reduction in state space, it still passes TestU01 BigCrush.
*
* get_uint32 is derived from M.E. O'Neill's minimal PCG implementation.
* That function (c) 2014 M.E. O'Neill / pcg-random.org
* Licensed under Apache License 2.0
*/

#include "AppHdr.h"

#include "pcg.h"

static PcgRNG pcg_rng[2];

PcgRNG &PcgRNG::generator(int which)
{
ASSERT(which >= 0);
ASSERT((size_t) which < ARRAYSZ(pcg_rng));
return pcg_rng[which];
}

uint32_t
PcgRNG::get_uint32()
{
uint64_t oldstate = state_;
// Advance internal state
state_ = oldstate * 6364136223846793005ULL + (inc_|1);
// Calculate output function (XSH RR), uses old state for max ILP
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
uint32_t rot = oldstate >> 59u;
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}

uint64_t
PcgRNG::get_uint64()
{
return static_cast<uint64_t>(get_uint32()) << 32 | get_uint32();
}

PcgRNG::PcgRNG()
{
// Choose base state arbitrarily. There's nothing up my sleeve.
state_ = 18446744073709551557ULL; // Largest 64-bit prime
inc_ = 2305843009213693951ULL; // Largest Mersenne prime under 64-bits
}

PcgRNG::PcgRNG(uint64_t init_key[], int key_length)
{

if (key_length > 0)
state_ = init_key[0];
if (key_length > 1)
{
inc_ = init_key[1];
}
else
{
inc_ ^= get_uint32();
}
}

uint32_t get_uint32(int generator)
{
return PcgRNG::generator(generator).get_uint32();
}

uint32_t get_uint64(int generator)
{
return PcgRNG::generator(generator).get_uint64();
}

void seed_rng(uint64_t seed_array[], int seed_len)
{
PcgRNG seeded(seed_array, seed_len);
// Use the just seeded RNG to initialize the rest.
for (size_t i = 0; i < ARRAYSZ(pcg_rng); ++i)
{
uint64_t key[2] = { seeded.get_uint64(), seeded.get_uint64() };
pcg_rng[i] = PcgRNG(key, ARRAYSZ(key));
}
}
@@ -0,0 +1,26 @@
#ifndef __LIB_PCG_HEADER__
#define __LIB_PCG_HEADER__

class PcgRNG
{
public:
PcgRNG();
PcgRNG(uint64_t init_key[], int key_length);
uint32_t get_uint32();
uint64_t get_uint64();
uint32_t operator()() { return get_uint32(); }

typedef uint32_t result_type;
static constexpr uint32_t min() { return 0; }
static constexpr uint32_t max() { return 0xffffffffU; }

static PcgRNG &generator(int which = 0);
private:
uint64_t state_;
uint64_t inc_;
};

uint32_t get_uint32(int generator = 0);
uint32_t get_uint64(int generator = 0);
void seed_rng(uint64_t[], int);
#endif
@@ -15,29 +15,29 @@
# include <process.h>
#endif

#include "asg.h"
#include "pcg.h"
#include "syscalls.h"

void seed_rng(uint32_t seed)
{
uint32_t sarg[1] = { seed };
seed_asg(sarg, 1);
uint64_t sarg[1] = { seed };
seed_rng(sarg, 1);
}

void seed_rng()
{
/* Use a 160-bit wide seed */
uint32_t seed_key[5];
uint64_t seed_key[2];
read_urandom((char*)(&seed_key), sizeof(seed_key));

#ifdef UNIX
struct tms buf;
seed_key[0] += times(&buf);
seed_key[0] ^= times(&buf);
#endif
seed_key[1] += getpid();
seed_key[2] += time(nullptr);
seed_key[1] ^= getpid();
seed_key[2] ^= time(nullptr);

seed_asg(seed_key, 5);
seed_rng(seed_key, 2);
}

uint32_t random_int()
@@ -301,9 +301,22 @@ int binomial(unsigned n_trials, unsigned trial_prob, unsigned scale)
}

// range [0, 1.0)
// This uses a technique described by Saito and Matsumoto at
// MCQMC'08. Given that the IEEE floating point numbers are
// uniformly distributed over [1,2), we generate a number in
// this range and then offset it onto the range [0,1). The
// choice of bits (masking v. shifting) is arbitrary and
// should be immaterial for high quality generators.
double random_real()
{
return get_uint32() / (1.0 + AsgKISS::max());
static const uint64_t UPPER_MASK = 0x3FF0000000000000ULL;
static const uint64_t LOWER_MASK = 0xFFFFFFFFFFFFFULL;
const uint64_t value = UPPER_MASK | (get_uint64() & LOWER_MASK);
double result;
// Portable memory transmutation. The union trick almost always
// works, but this is safer.
memcpy(&result, &value, sizeof(value));
return result - 1.0;
}

// Roll n_trials, return true if at least one succeeded. n_trials might be
@@ -3,7 +3,6 @@
#include "tileview.h"

#include "areas.h"
#include "asg.h"
#include "branch.h"
#include "cloud.h"
#include "colour.h"
@@ -18,6 +17,7 @@
#include "kills.h"
#include "mon-util.h"
#include "options.h"
#include "pcg.h"
#include "player.h"
#include "state.h"
#include "terrain.h"
@@ -300,12 +300,12 @@ void tile_init_flavour()
vector<unsigned int> output;
{
domino::DominoSet<domino::EdgeDomino> dominoes(domino::cohen_set, 8);
uint32_t seed[] =
uint64_t seed[] =
{
static_cast<uint32_t>(ui_random(INT_MAX)),
static_cast<uint32_t>(ui_random(INT_MAX)),
static_cast<uint64_t>(ui_random(INT_MAX)) << 32 | ui_random(INT_MAX),
static_cast<uint64_t>(ui_random(INT_MAX)) << 32 | ui_random(INT_MAX),
};
AsgKISS rng(seed, 2);
PcgRNG rng(seed, 2);
dominoes.Generate(X_WIDTH, Y_WIDTH, output, rng);
}
for (rectangle_iterator ri(0); ri; ++ri)

0 comments on commit 415d666

Please sign in to comment.
You can’t perform that action at this time.