Add cross-platform randomizer module.

The code uses two mechanisms for generating random numbers: srand()/rand(),
which is not thread-safe, and srandom()/random(), which is POSIX-specific.

Here I add a util/random.cc module that centralizes these calls, and unifies
some common usage patterns.  If the implementation is not good enough, we can
now change it in a single place.

To keep things simple, this uses the portable srand()/rand() but protects them
with a lock to avoid concurrency problems.

The hard part was to keep the regression tests passing: they rely on fixed
sequences of random numbers, so a small code change could break them very
thoroughly.  Util::rand(), for wide types like size_t, calls std::rand() not
once but twice.  This behaviour was generalized into utils::wide_rand() and
friends.
This commit is contained in:
Jeroen Vermeulen 2015-04-23 19:27:21 +07:00
parent 4b47e1148c
commit 38d790cac0
24 changed files with 452 additions and 117 deletions

View File

@ -25,6 +25,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "moses/Word.h" #include "moses/Word.h"
#include "moses/FF/FeatureFunction.h" #include "moses/FF/FeatureFunction.h"
#include "Decoder.h" #include "Decoder.h"
#include "util/random.hh"
typedef std::map<const Moses::FeatureFunction*, std::vector< float > > ProducerWeightMap; typedef std::map<const Moses::FeatureFunction*, std::vector< float > > ProducerWeightMap;
typedef std::pair<const Moses::FeatureFunction*, std::vector< float > > ProducerWeightPair; typedef std::pair<const Moses::FeatureFunction*, std::vector< float > > ProducerWeightPair;
@ -37,8 +38,11 @@ template <class T> bool from_string(T& t, const std::string& s, std::ios_base& (
struct RandomIndex { struct RandomIndex {
ptrdiff_t operator()(ptrdiff_t max) { ptrdiff_t operator()(ptrdiff_t max) {
srand(time(0)); // Initialize random number generator with current time. // TODO: Don't seed the randomizer here. If this function gets called
return static_cast<ptrdiff_t> (rand() % max); // multiple times in the same second, it will return the same value on
// each of those calls.
util::rand_init();
return util::rand_excl(max);
} }
}; };

View File

@ -42,6 +42,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "RelativeEntropyCalc.h" #include "RelativeEntropyCalc.h"
#include "LexicalReordering.h" #include "LexicalReordering.h"
#include "LexicalReorderingState.h" #include "LexicalReorderingState.h"
#include "util/random.hh"
#ifdef HAVE_PROTOBUF #ifdef HAVE_PROTOBUF
#include "hypergraph.pb.h" #include "hypergraph.pb.h"
@ -205,7 +206,7 @@ int main(int argc, char** argv)
//initialise random numbers //initialise random numbers
srand(time(NULL)); rand_init();
// set up read/writing class // set up read/writing class
IOWrapper* ioWrapper = GetIOWrapper(staticData); IOWrapper* ioWrapper = GetIOWrapper(staticData);

View File

@ -287,7 +287,7 @@ void Data::createShards(size_t shard_count, float shard_size, const string& scor
} else { } else {
//create shards by randomly sampling //create shards by randomly sampling
for (size_t i = 0; i < floor(shard_size+0.5); ++i) { for (size_t i = 0; i < floor(shard_size+0.5); ++i) {
shard_contents.push_back(util::rand_int() % data_size); shard_contents.push_back(util::rand_excl(data_size));
} }
} }

View File

@ -58,10 +58,8 @@ void Point::Randomize()
UTIL_THROW_IF(m_min.size() != Point::m_dim, util::Exception, "Error"); UTIL_THROW_IF(m_min.size() != Point::m_dim, util::Exception, "Error");
UTIL_THROW_IF(m_max.size() != Point::m_dim, util::Exception, "Error"); UTIL_THROW_IF(m_max.size() != Point::m_dim, util::Exception, "Error");
for (unsigned int i = 0; i < size(); i++) { for (unsigned int i = 0; i < size(); i++)
const float scale = (m_max[i] - m_min[i]) / float(RAND_MAX); operator[](i) = util::rand_incl(m_min[i], m_max[i]);
operator[](i) = m_min[i] + util::rand_int() * scale;
}
} }
double Point::operator*(const FeatureStats& F) const double Point::operator*(const FeatureStats& F) const

View File

@ -5,11 +5,8 @@
- check that --pairwise-ranked is compatible with all optimization metrics - check that --pairwise-ranked is compatible with all optimization metrics
- Replace the standard rand() currently used in MERT and PRO with better - Use better random generators in util/random.cc, e.g. boost::mt19937.
random generators such as Boost's random generators (e.g., boost::mt19937). - Support plugging of custom random generators.
- create a Random class to hide the details, i.e., how to generate
random numbers, which allows us to use custom random generators more
easily.
Pros: Pros:
- In MERT, you might want to use the random restarting technique to avoid - In MERT, you might want to use the random restarting technique to avoid

View File

@ -95,7 +95,7 @@ void EvaluatorUtil::evaluate(const string& candFile, int bootstrap, bool nbest_i
for (int i = 0; i < bootstrap; ++i) { for (int i = 0; i < bootstrap; ++i) {
ScoreData scoredata(g_scorer); ScoreData scoredata(g_scorer);
for (int j = 0; j < n; ++j) { for (int j = 0; j < n; ++j) {
int randomIndex = util::rand_int() % n; const int randomIndex = util::rand_excl(n);
scoredata.add(entries[randomIndex], j); scoredata.add(entries[randomIndex], j);
} }
g_scorer->setScoreData(&scoredata); g_scorer->setScoreData(&scoredata);
@ -285,10 +285,10 @@ void InitSeed(const ProgramOption *opt)
{ {
if (opt->has_seed) { if (opt->has_seed) {
cerr << "Seeding random numbers with " << opt->seed << endl; cerr << "Seeding random numbers with " << opt->seed << endl;
util::rand_int_init(opt->seed); util::rand_init(opt->seed);
} else { } else {
cerr << "Seeding random numbers with system clock " << endl; cerr << "Seeding random numbers with system clock " << endl;
util::rand_int_init(); util::rand_init();
} }
} }

View File

@ -40,6 +40,7 @@ de recherches du Canada
#include <boost/scoped_ptr.hpp> #include <boost/scoped_ptr.hpp>
#include "util/exception.hh" #include "util/exception.hh"
#include "util/random.hh"
#include "BleuScorer.h" #include "BleuScorer.h"
#include "HopeFearDecoder.h" #include "HopeFearDecoder.h"
@ -122,10 +123,10 @@ int main(int argc, char** argv)
if (vm.count("random-seed")) { if (vm.count("random-seed")) {
cerr << "Initialising random seed to " << seed << endl; cerr << "Initialising random seed to " << seed << endl;
srand(seed); util::rand_init(seed);
} else { } else {
cerr << "Initialising random seed from system clock" << endl; cerr << "Initialising random seed from system clock" << endl;
srand(time(NULL)); util::rand_init();
} }
// Initialize weights // Initialize weights

View File

@ -290,10 +290,10 @@ int main(int argc, char **argv)
if (option.has_seed) { if (option.has_seed) {
cerr << "Seeding random numbers with " << option.seed << endl; cerr << "Seeding random numbers with " << option.seed << endl;
util::rand_int_init(option.seed); util::rand_init(option.seed);
} else { } else {
cerr << "Seeding random numbers with system clock " << endl; cerr << "Seeding random numbers with system clock " << endl;
util::rand_int_init(); util::rand_init();
} }
if (option.sparse_weights_file.size()) ++option.pdim; if (option.sparse_weights_file.size()) ++option.pdim;

View File

@ -43,6 +43,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "ScoreDataIterator.h" #include "ScoreDataIterator.h"
#include "BleuScorer.h" #include "BleuScorer.h"
#include "Util.h" #include "Util.h"
#include "util/random.hh"
using namespace std; using namespace std;
using namespace MosesTuning; using namespace MosesTuning;
@ -141,10 +142,10 @@ int main(int argc, char** argv)
if (vm.count("random-seed")) { if (vm.count("random-seed")) {
cerr << "Initialising random seed to " << seed << endl; cerr << "Initialising random seed to " << seed << endl;
srand(seed); util::rand_init(seed);
} else { } else {
cerr << "Initialising random seed from system clock" << endl; cerr << "Initialising random seed from system clock" << endl;
srand(time(NULL)); util::rand_init();
} }
if (scoreFiles.size() == 0 || featureFiles.size() == 0) { if (scoreFiles.size() == 0 || featureFiles.size() == 0) {
@ -211,11 +212,11 @@ int main(int argc, char** argv)
vector<float> scores; vector<float> scores;
size_t n_translations = hypotheses.size(); size_t n_translations = hypotheses.size();
for(size_t i=0; i<n_candidates; i++) { for(size_t i=0; i<n_candidates; i++) {
size_t rand1 = rand() % n_translations; size_t rand1 = util::rand_excl(n_translations);
pair<size_t,size_t> translation1 = hypotheses[rand1]; pair<size_t,size_t> translation1 = hypotheses[rand1];
float bleu1 = smoothedSentenceBleu(scoreDataIters[translation1.first]->operator[](translation1.second), bleuSmoothing, smoothBP); float bleu1 = smoothedSentenceBleu(scoreDataIters[translation1.first]->operator[](translation1.second), bleuSmoothing, smoothBP);
size_t rand2 = rand() % n_translations; size_t rand2 = util::rand_excl(n_translations);
pair<size_t,size_t> translation2 = hypotheses[rand2]; pair<size_t,size_t> translation2 = hypotheses[rand2];
float bleu2 = smoothedSentenceBleu(scoreDataIters[translation2.first]->operator[](translation2.second), bleuSmoothing, smoothBP); float bleu2 = smoothedSentenceBleu(scoreDataIters[translation2.first]->operator[](translation2.second), bleuSmoothing, smoothBP);

View File

@ -45,6 +45,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "moses/FF/StatefulFeatureFunction.h" #include "moses/FF/StatefulFeatureFunction.h"
#include "moses/FF/StatelessFeatureFunction.h" #include "moses/FF/StatelessFeatureFunction.h"
#include "moses/TrainingTask.h" #include "moses/TrainingTask.h"
#include "util/random.hh"
#ifdef HAVE_PROTOBUF #ifdef HAVE_PROTOBUF
#include "hypergraph.pb.h" #include "hypergraph.pb.h"
@ -117,7 +118,7 @@ int main(int argc, char** argv)
//initialise random numbers //initialise random numbers
srand(time(NULL)); util::rand_init();
// set up read/writing class // set up read/writing class
IFVERBOSE(1) { IFVERBOSE(1) {

View File

@ -27,6 +27,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include <sstream> #include <sstream>
#include <vector> #include <vector>
#include "util/random.hh"
#include "util/usage.hh" #include "util/usage.hh"
#ifdef WIN32 #ifdef WIN32
@ -91,7 +92,7 @@ SimpleTranslationInterface::SimpleTranslationInterface(const string &mosesIni):
exit(1); exit(1);
} }
srand(time(NULL)); util::rand_init();
} }
@ -185,7 +186,7 @@ batch_run()
const StaticData& staticData = StaticData::Instance(); const StaticData& staticData = StaticData::Instance();
//initialise random numbers //initialise random numbers
srand(time(NULL)); util::rand_init();
IFVERBOSE(1) PrintUserTime("Created input-output object"); IFVERBOSE(1) PrintUserTime("Created input-output object");

View File

@ -54,6 +54,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#endif #endif
#include "util/exception.hh" #include "util/exception.hh"
#include "util/random.hh"
using namespace std; using namespace std;
@ -418,7 +419,7 @@ void Manager::CalcLatticeSamples(size_t count, TrellisPathList &ret) const
//cerr << endl; //cerr << endl;
//draw the sample //draw the sample
float frandom = log((float)rand()/RAND_MAX); const float frandom = log(util::rand_incl(0.0f, 1.0f));
size_t position = 1; size_t position = 1;
float sum = candidateScores[0]; float sum = candidateScores[0];
for (; position < candidateScores.size() && sum < frandom; ++position) { for (; position < candidateScores.size() && sum < frandom; ++position) {

View File

@ -31,6 +31,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "InputFileStream.h" #include "InputFileStream.h"
#include "StaticData.h" #include "StaticData.h"
#include "util/exception.hh" #include "util/exception.hh"
#include "util/random.hh"
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
@ -1392,7 +1393,7 @@ struct Credit {
this->contact = contact ; this->contact = contact ;
this->currentPursuits = currentPursuits ; this->currentPursuits = currentPursuits ;
this->areaResponsibility = areaResponsibility; this->areaResponsibility = areaResponsibility;
this->sortId = rand() % 1000; this->sortId = util::rand_excl(1000);
} }
bool operator<(const Credit &other) const { bool operator<(const Credit &other) const {

View File

@ -6,6 +6,7 @@
#include "utils.h" #include "utils.h"
#include "FileHandler.h" #include "FileHandler.h"
#include "util/exception.hh" #include "util/exception.hh"
#include "util/random.hh"
using namespace Moses; using namespace Moses;
typedef uint64_t P; // largest input range is 2^64 typedef uint64_t P; // largest input range is 2^64
@ -162,7 +163,7 @@ void Hash_shiftAddXOR<T>::initSeeds()
{ {
v_ = new T[this->H_]; v_ = new T[this->H_];
for(count_t i=0; i < this->H_; i++) for(count_t i=0; i < this->H_; i++)
v_[i] = Utils::rand<T>() + 1; v_[i] = util::wide_rand<T>() + 1;
} }
template <typename T> template <typename T>
T Hash_shiftAddXOR<T>::hash(const char* s, count_t h) T Hash_shiftAddXOR<T>::hash(const char* s, count_t h)
@ -187,9 +188,8 @@ void UnivHash_tableXOR<T>::initSeeds()
// fill with random values // fill with random values
for(count_t j=0; j < this->H_; j++) { for(count_t j=0; j < this->H_; j++) {
table_[j] = new T[tblLen_]; table_[j] = new T[tblLen_];
for(count_t i=0; i < tblLen_; i++) { for(count_t i=0; i < tblLen_; i++)
table_[j][i] = Utils::rand<T>(this->m_-1); table_[j][i] = util::wide_rand_excl(this->m_-1);
}
} }
} }
template <typename T> template <typename T>
@ -218,7 +218,7 @@ void UnivHash_noPrimes<T>::initSeeds()
{ {
a_ = new P[this->H_]; a_ = new P[this->H_];
for(T i=0; i < this->H_; i++) { for(T i=0; i < this->H_; i++) {
a_[i] = Utils::rand<P>(); a_[i] = util::wide_rand<P>();
if(a_[i] % 2 == 0) a_[i]++; // a must be odd if(a_[i] % 2 == 0) a_[i]++; // a must be odd
} }
} }
@ -284,8 +284,8 @@ void UnivHash_linear<T>::initSeeds()
a_[i] = new T[MAX_NGRAM_ORDER]; a_[i] = new T[MAX_NGRAM_ORDER];
b_[i] = new T[MAX_NGRAM_ORDER]; b_[i] = new T[MAX_NGRAM_ORDER];
for(count_t j=0; j < MAX_NGRAM_ORDER; j++) { for(count_t j=0; j < MAX_NGRAM_ORDER; j++) {
a_[i][j] = 1 + Utils::rand<T>(); a_[i][j] = 1 + util::wide_rand<T>();
b_[i][j] = Utils::rand<T>(); b_[i][j] = util::wide_rand<T>();
} }
} }
} }

View File

@ -62,22 +62,6 @@ public:
str[i] = tolower(str[i]); str[i] = tolower(str[i]);
} }
} }
// TODO: interface with decent PRG
template<typename T>
static T rand(T mod_bnd = 0) {
T random = 0;
if(sizeof(T) <= 4) {
random = static_cast<T>(std::rand());
} else if(sizeof(T) == 8) {
random = static_cast<T>(std::rand());
random <<= 31;
random <<= 1;
random |= static_cast<T>(std::rand());
}
if(mod_bnd != 0)
return random % mod_bnd;
else return random;
}
}; };
#endif #endif

View File

@ -1,4 +1,6 @@
#include "DynSuffixArray.h" #include "DynSuffixArray.h"
#include "util/random.hh"
#include <iostream> #include <iostream>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
@ -315,33 +317,31 @@ int DynSuffixArray::Compare(int pos1, int pos2, int max)
return 0; return 0;
} }
namespace
{
/// Helper: swap two entries in an int array.
inline void swap_ints(int array[], int one, int other)
{
const int tmp = array[one];
array[one] = array[other];
array[other] = tmp;
}
}
void DynSuffixArray::Qsort(int* array, int begin, int end) void DynSuffixArray::Qsort(int* array, int begin, int end)
{ {
if(end > begin) { if(end > begin) {
int index; int index = util::rand_incl(begin, end);
{ {
index = begin + (rand() % (end - begin + 1)); const int pivot = array[index];
int pivot = array[index]; swap_ints(array, index, end);
{
int tmp = array[index];
array[index] = array[end];
array[end] = tmp;
}
for(int i=index=begin; i < end; ++i) { for(int i=index=begin; i < end; ++i) {
if (Compare(array[i], pivot, 20) <= 0) { if (Compare(array[i], pivot, 20) <= 0) {
{ swap_ints(array, index, i);
int tmp = array[index];
array[index] = array[i];
array[i] = tmp;
index++; index++;
} }
} }
} swap_ints(array, index, end);
{
int tmp = array[index];
array[index] = array[end];
array[end] = tmp;
}
} }
Qsort(array, begin, index - 1); Qsort(array, begin, index - 1);
Qsort(array, index + 1, end); Qsort(array, index + 1, end);

View File

@ -45,6 +45,7 @@
#include "moses/TranslationModel/fuzzy-match/SentenceAlignment.h" #include "moses/TranslationModel/fuzzy-match/SentenceAlignment.h"
#include "util/file.hh" #include "util/file.hh"
#include "util/exception.hh" #include "util/exception.hh"
#include "util/random.hh"
using namespace std; using namespace std;
@ -62,8 +63,8 @@ char *mkdtemp(char *tempbuf)
return NULL; return NULL;
} }
srand((unsigned)time(0)); util::rand_init();
rand_value = (int)((rand() / ((double)RAND_MAX+1.0)) * 1e6); rand_value = rand_excl(1e6);
tempbase = strrchr(tempbuf, '/'); tempbase = strrchr(tempbuf, '/');
tempbase = tempbase ? tempbase+1 : tempbuf; tempbase = tempbase ? tempbase+1 : tempbuf;
strcpy(tempbasebuf, tempbase); strcpy(tempbasebuf, tempbase);

View File

@ -2,19 +2,16 @@
#define __sampling_h #define __sampling_h
#include <boost/dynamic_bitset.hpp> #include <boost/dynamic_bitset.hpp>
#include <vector> #include <vector>
#include "util/random.hh"
// Utility functions for proper sub-sampling. // Utility functions for proper sub-sampling.
// (c) 2007-2012 Ulrich Germann // (c) 2007-2012 Ulrich Germann
namespace Moses namespace Moses
{ {
using namespace std; using namespace std;
inline
size_t
randInt(size_t N)
{
return N*(rand()/(RAND_MAX+1.));
}
// select a random sample of size /s/ without restitution from the range of // select a random sample of size /s/ without restitution from the range of
// integers [0,N); // integers [0,N);
@ -35,15 +32,15 @@ randomSample(vector<idx_t>& v, size_t s, size_t N)
if (s*10<N) { if (s*10<N) {
boost::dynamic_bitset<uint64_t> check(N,0); boost::dynamic_bitset<uint64_t> check(N,0);
for (size_t i = 0; i < v.size(); i++) { for (size_t i = 0; i < v.size(); i++) {
size_t x = randInt(N); size_t x = util::rand_excl(N);
while (check[x]) x = randInt(N); while (check[x]) x = util::rand_excl(N);
check[x]=true; check[x]=true;
v[i] = x; v[i] = x;
} }
} else { } else {
size_t m=0; size_t m=0;
for (size_t t = 0; m <= s && t < N; t++) for (size_t t = 0; m <= s && t < N; t++)
if (s==N || randInt(N-t) < s-m) v[m++] = t; if (s==N || util::rand_excl(N-t) < s-m) v[m++] = t;
} }
} }

View File

@ -345,7 +345,7 @@
// { // {
// boost::lock_guard<boost::mutex> lock(stats->lock); // boost::lock_guard<boost::mutex> lock(stats->lock);
// if (stats->raw_cnt == ctr) ++stats->raw_cnt; // if (stats->raw_cnt == ctr) ++stats->raw_cnt;
// size_t rnum = randInt(stats->raw_cnt - ctr++); // size_t rnum = util::rand_excl(stats->raw_cnt - ctr++);
// // cout << stats->raw_cnt << " " << ctr-1 << " " // // cout << stats->raw_cnt << " " << ctr-1 << " "
// // << rnum << " " << max_samples - stats->good << endl; // // << rnum << " " << max_samples - stats->good << endl;
// if (rnum < max_samples - stats->good) // if (rnum < max_samples - stats->good)

View File

@ -69,7 +69,7 @@ namespace ugdiss
// while (chosen < samplesize && next < stop) // while (chosen < samplesize && next < stop)
// { // {
// root->readEntry(next,*this); // root->readEntry(next,*this);
// if (randInt(N - sampled++) < samplesize - chosen) // if (util::rand_excl(N - sampled++) < samplesize - chosen)
// { // {
// ++chosen; // ++chosen;
// return true; // return true;

View File

@ -9,6 +9,7 @@
#include <iostream> #include <iostream>
#include "util/exception.hh" #include "util/exception.hh"
#include "moses/Util.h" #include "moses/Util.h"
#include "util/random.hh"
//#include <cassert> //#include <cassert>
// #include "ug_bv_iter.h" // #include "ug_bv_iter.h"
@ -894,13 +895,6 @@ namespace ugdiss
return bv; return bv;
} }
inline
size_t
randInt(size_t N)
{
return size_t(N*(rand()/(RAND_MAX+1.)));
}
/// randomly select up to N occurrences of the sequence /// randomly select up to N occurrences of the sequence
template<typename Token> template<typename Token>
sptr<vector<typename ttrack::Position> > sptr<vector<typename ttrack::Position> >
@ -922,8 +916,8 @@ namespace ugdiss
root->readEntry(I.next,I); root->readEntry(I.next,I);
// t: expected number of remaining samples // t: expected number of remaining samples
double t = (stop - I.pos)/root->aveIndexEntrySize(); const double t = (stop - I.pos)/root->aveIndexEntrySize();
double r = t*rand()/(RAND_MAX+1.); const double r = util::rand_excl(t);
if (r < N-m) if (r < N-m)
{ {
ret->at(m).offset = I.offset; ret->at(m).offset = I.offset;

View File

@ -19,21 +19,25 @@ namespace
boost::mutex rand_lock; boost::mutex rand_lock;
} // namespace } // namespace
void rand_int_init(unsigned int seed) void rand_init(unsigned int seed)
{ {
boost::lock_guard<boost::mutex> lock(rand_lock); boost::lock_guard<boost::mutex> lock(rand_lock);
srand(seed); srand(seed);
} }
void rand_int_init() void rand_init()
{ {
rand_int_init(time(NULL)); rand_init(time(NULL));
} }
namespace internal
{
// This is the one call to the actual randomizer. All else is built on this.
int rand_int() int rand_int()
{ {
boost::lock_guard<boost::mutex> lock(rand_lock); boost::lock_guard<boost::mutex> lock(rand_lock);
return rand(); return std::rand();
} }
} // namespace internal
} // namespace util } // namespace util

View File

@ -1,32 +1,229 @@
#ifndef UTIL_RANDOM_H #ifndef UTIL_RANDOM_H
#define UTIL_RANDOM_H #define UTIL_RANDOM_H
#include <cstdlib>
#include <limits>
namespace util namespace util
{ {
/** Thread-safe, cross-platform random number generator.
*
* This is not for proper security-grade randomness, but should be "good
* enough" for producing arbitrary values of various numeric types.
*
* Before starting, call rand_init() to seed the randomizer. There is no need
* to do this more than once; in fact doing it more often is likely to make the
* randomizer less effective. Once that is done, call the rand(), rand_excl(),
* and rand_incl() functions as needed to generate pseudo-random numbers.
*
* Probability distribution is roughly uniform, but for integral types is
* skewed slightly towards lower numbers depending on how close "top" comes to
* RAND_MAX.
*
* For floating-point types, resolution is limited; there will actually be
* only RAND_MAX different possible values.
*/
/** Initialize randomizer with a fixed seed. /** Initialize randomizer with a fixed seed.
* *
* After this, unless the randomizer gets seeded again, consecutive calls to * After this, unless the randomizer gets seeded again, consecutive calls to
* rand_int() will return a sequence of pseudo-random numbers determined by * the random functions will return a sequence of pseudo-random numbers
* the seed. Every time the randomizer is seeded with this same seed, it will * determined by the seed. Every time the randomizer is seeded with this same
* again start returning the same sequence of numbers. * seed, it will again start returning the same sequence of numbers.
*/ */
void rand_int_init(unsigned int); void rand_init(unsigned int);
/** Initialize randomizer based on current time. /** Initialize randomizer based on current time.
* *
* Call this to make the randomizer return hard-to-predict numbers. It won't * Call this to make the randomizer return hard-to-predict numbers. It won't
* produce high-grade randomness, but enough to make the program act * produce high-grade randomness, but enough to make the program act
* differently on different runs. * differently on different runs.
*
* The seed will be based on the current time in seconds. So calling it twice
* within the same second will just reset the randomizer to where it was before.
* Don't do that.
*/ */
void rand_int_init(); void rand_init();
/** Return a pseudorandom number between 0 and RAND_MAX inclusive. /** Return a pseudorandom number between 0 and RAND_MAX inclusive.
* *
* Initialize (seed) the randomizer before starting to call this. * Initialize (seed) the randomizer before starting to call this.
*/ */
template<typename T> inline T rand();
/** Return a pseudorandom number in the half-open interval [bottom, top).
*
* Generates a value between "bottom" (inclusive) and "top" (exclusive),
* assuming that (top - bottom) <= RAND_MAX.
*/
template<typename T> inline T rand_excl(T bottom, T top);
/** Return a pseudorandom number in the half-open interval [0, top).
*
* Generates a value between 0 (inclusive) and "top" (exclusive), assuming that
* bottom <= RAND_MAX.
*/
template<typename T> inline T rand_excl(T top);
/** Return a pseudorandom number in the open interval [bottom, top].
*
* Generates a value between "bottom" and "top" inclusive, assuming that
* (top - bottom) < RAND_MAX.
*/
template<typename T> inline T rand_incl(T bottom, T top);
/** Return a pseudorandom number in the open interval [0, top].
*
* Generates a value between 0 and "top" inclusive, assuming that
* bottom < RAND_MAX.
*/
template<typename T> inline T rand_incl(T top);
/** Return a pseudorandom number which may be larger than RAND_MAX.
*
* The requested type must be integral, and its size must be an even multiple
* of the size of an int. The return value will combine one or more random
* ints into a single value, which could get quite large.
*
* The result is nonnegative. Because the constituent ints are also
* nonnegative, the most significant bit in each of the ints will be zero,
* so for a wider type, there will be "gaps" in the range of possible outputs.
*/
template<typename T> inline T wide_rand();
/** Return a pseudorandom number in [0, top), not limited to RAND_MAX.
*
* Works like wide_rand(), but if the requested type is wider than an int, it
* accommodates larger top values than an int can represent.
*/
template<typename T> inline T wide_rand_excl(T top);
/** Return a pseudorandom number in [bottom, top), not limited to RAND_MAX.
*
* Works like wide_rand(), but if the requested type is wider than an int, it
* accommodates larger value ranges than an int can represent.
*/
template<typename T> inline T wide_rand_excl(T bottom, T top);
/** Return a pseudorandom number in [0, top], not limited to RAND_MAX.
*
* Works like wide_rand(), but if the requested type is wider than an int, it
* accommodates larger top values than an int can represent.
*/
template<typename T> inline T wide_rand_incl(T top);
/** Return a pseudorandom number in [bottom, top], not limited to RAND_MAX.
*
* Works like wide_rand(), but if the requested type is wider than an int, it
* accommodates larger top values than an int can represent.
*/
template<typename T> inline T wide_rand_incl(T bottom, T top);
/// Implementation detail. For the random module's internal use only.
namespace internal
{
/// The central call to the randomizer upon which this whole module is built.
int rand_int(); int rand_int();
/// Helper template: customize random values to required ranges.
template<typename T, bool is_integer_type> struct random_scaler;
/// Specialized random_scaler for integral types.
template<typename T> struct random_scaler<T, true>
{
static T rnd_excl(T value, T range) { return value % range; }
static T rnd_incl(T value, T range) { return value % (range + 1); }
};
/// Specialized random_scaler for non-integral types.
template<typename T> struct random_scaler<T, false>
{
static T rnd_excl(T value, T range)
{
// Promote RAND_MAX to T before adding one to avoid overflow.
return range * value / (T(RAND_MAX) + 1);
}
static T rnd_incl(T value, T range) { return range * value / RAND_MAX; }
};
/// Helper for filling a wider variable with random ints.
template<typename T, size_t remaining_ints> struct wide_random_collector
{
static T generate()
{
T one_int = util::rand<T>() << (8 * sizeof(int));
return one_int | wide_random_collector<T, remaining_ints-1>::generate();
}
};
/// Specialized wide_random_collector for generating just a single int.
template<typename T> struct wide_random_collector<T, 1>
{
static T generate() { return util::rand<T>(); }
};
} // namespace internal
template<typename T> inline T rand()
{
return T(util::internal::rand_int());
}
template<typename T> inline T rand_excl(T top)
{
typedef internal::random_scaler<T, std::numeric_limits<T>::is_integer> scaler;
return scaler::rnd_excl(util::rand<T>(), top);
}
template<typename T> inline T rand_excl(T bottom, T top)
{
return bottom + rand_excl(top - bottom);
}
template<typename T> inline T rand_incl(T top)
{
typedef internal::random_scaler<T, std::numeric_limits<T>::is_integer> scaler;
return scaler::rnd_incl(util::rand<T>(), top);
}
template<typename T> inline T rand_incl(T bottom, T top)
{
return bottom + rand_incl(top - bottom);
}
template<typename T> inline T wide_rand()
{
return internal::wide_random_collector<T, sizeof(T)/sizeof(int)>::generate();
}
template<typename T> inline T wide_rand_excl(T top)
{
typedef internal::random_scaler<T, std::numeric_limits<T>::is_integer> scaler;
return scaler::rnd_excl(util::wide_rand<T>(), top);
}
template<typename T> inline T wide_rand_excl(T bottom, T top)
{
return bottom + wide_rand_excl(top - bottom);
}
template<typename T> inline T wide_rand_incl(T top)
{
typedef internal::random_scaler<T, std::numeric_limits<T>::is_integer> scaler;
return scaler::rnd_incl(util::wide_rand<T>(), top);
}
template<typename T> inline T wide_rand_incl(T bottom, T top)
{
return bottom + wide_rand_incl(top - bottom);
}
} // namespace util } // namespace util
#endif #endif

View File

@ -1,3 +1,5 @@
#include <cstdlib>
#include "util/random.hh" #include "util/random.hh"
#define BOOST_TEST_MODULE RandomTest #define BOOST_TEST_MODULE RandomTest
@ -8,32 +10,182 @@ namespace util
namespace namespace
{ {
BOOST_AUTO_TEST_CASE(returns_different_consecutive_numbers) BOOST_AUTO_TEST_CASE(rand_int_returns_positive_no_greater_than_RAND_MAX)
{ {
rand_int_init(99); rand_init();
const int first = rand_int(), second = rand_int(), third = rand_int(); for (int i=0; i<100; i++)
{
const int random_number = rand<int>();
BOOST_CHECK(random_number >= 0);
BOOST_CHECK(random_number <= RAND_MAX);
}
}
BOOST_AUTO_TEST_CASE(rand_int_returns_different_consecutive_numbers)
{
rand_init(99);
const int first = rand<int>(), second = rand<int>(), third = rand<int>();
// Sometimes you'll get the same number twice in a row, but generally the // Sometimes you'll get the same number twice in a row, but generally the
// randomizer returns different numbers. // randomizer returns different numbers.
BOOST_CHECK(second != first || third != first); BOOST_CHECK(second != first || third != first);
} }
BOOST_AUTO_TEST_CASE(returns_different_numbers_for_different_seeds) BOOST_AUTO_TEST_CASE(rand_int_returns_different_numbers_for_different_seeds)
{ {
rand_int_init(1); rand_init(1);
const int one1 = rand_int(), one2 = rand_int(); const int one1 = rand<int>(), one2 = rand<int>();
rand_int_init(2); rand_init(2);
const int two1 = rand_int(), two2 = rand_int(); const int two1 = rand<int>(), two2 = rand<int>();
BOOST_CHECK(two1 != one1 || two2 != two1); BOOST_CHECK(two1 != one1 || two2 != one2);
} }
BOOST_AUTO_TEST_CASE(returns_same_sequence_for_same_seed) BOOST_AUTO_TEST_CASE(rand_int_returns_same_sequence_for_same_seed)
{ {
rand_int_init(1); rand_init(1);
const int first = rand_int(); const int first = rand<int>();
rand_int_init(1); rand_init(1);
const int second = rand_int(); const int second = rand<int>();
BOOST_CHECK_EQUAL(first, second); BOOST_CHECK_EQUAL(first, second);
} }
BOOST_AUTO_TEST_CASE(rand_excl_int_returns_number_in_range)
{
const int bottom = 10, top = 50;
for (int i=0; i<100; i++)
{
const int random_number = rand_excl(bottom, top);
BOOST_CHECK(random_number >= bottom);
BOOST_CHECK(random_number < top);
}
}
BOOST_AUTO_TEST_CASE(rand_excl_int_covers_full_range)
{
// The spread of random numbers really goes all the way from 0 (inclusive)
// to "top" (exclusive). It's not some smaller subset.
// This test will randomly fail sometimes, though very very rarely, when the
// random numbers don't actually have enough different values.
const int bottom = 1, top = 4;
int lowest = 99, highest = -1;
for (int i=0; i<100; i++)
{
const int random_number = rand_excl(bottom, top);
lowest = std::min(lowest, random_number);
highest = std::max(highest, random_number);
}
BOOST_CHECK_EQUAL(lowest, bottom);
BOOST_CHECK_EQUAL(highest, top - 1);
}
BOOST_AUTO_TEST_CASE(rand_incl_int_returns_number_in_range)
{
const int bottom = 10, top = 50;
for (int i=0; i<100; i++)
{
const int random_number = rand_incl(bottom, top);
BOOST_CHECK(random_number >= 0);
BOOST_CHECK(random_number <= top);
}
}
BOOST_AUTO_TEST_CASE(rand_incl_int_covers_full_range)
{
// The spread of random numbers really goes all the way from 0 to "top"
// inclusive. It's not some smaller subset.
// This test will randomly fail sometimes, though very very rarely, when the
// random numbers don't actually have enough different values.
const int bottom = 1, top = 4;
int lowest = 99, highest = -1;
for (int i=0; i<100; i++)
{
const int random_number = rand_incl(bottom, top);
lowest = std::min(lowest, random_number);
highest = std::max(highest, random_number);
}
BOOST_CHECK_EQUAL(lowest, bottom);
BOOST_CHECK_EQUAL(highest, top);
}
BOOST_AUTO_TEST_CASE(rand_excl_float_returns_float_in_range)
{
const float bottom = 5, top = 10;
for (int i=0; i<100; i++)
{
const float random_number = rand_excl(bottom, top);
BOOST_CHECK(random_number >= bottom);
BOOST_CHECK(random_number < top);
}
}
BOOST_AUTO_TEST_CASE(rand_excl_float_returns_different_values)
{
const float bottom = 5, top = 10;
float lowest = 99, highest = -1;
for (int i=0; i<10; i++)
{
const float random_number = rand_excl(bottom, top);
lowest = std::min(lowest, random_number);
highest = std::max(highest, random_number);
}
BOOST_CHECK(lowest < highest);
}
BOOST_AUTO_TEST_CASE(rand_float_incl_returns_float_in_range)
{
const float bottom = 5, top = 10;
for (int i=0; i<1000; i++)
{
const float random_number = rand_excl(bottom, top);
BOOST_CHECK(random_number >= bottom);
BOOST_CHECK(random_number <= top);
}
}
BOOST_AUTO_TEST_CASE(rand_float_incl_returns_different_values)
{
const float bottom = 0, top = 10;
float lowest = 99, highest = -1;
for (int i=0; i<10; i++)
{
const float random_number = rand_excl(bottom, top);
lowest = std::min(lowest, random_number);
highest = std::max(highest, random_number);
}
BOOST_CHECK(lowest < highest);
}
BOOST_AUTO_TEST_CASE(wide_rand_int_returns_different_numbers_in_range)
{
for (int i=0; i<100; i++)
{
const int random_number = wide_rand<int>();
BOOST_CHECK(random_number >= 0);
BOOST_CHECK(random_number <= RAND_MAX);
}
}
BOOST_AUTO_TEST_CASE(wide_rand_long_long_returns_big_numbers)
{
long long one = wide_rand<long long>(), two = wide_rand<long long>();
// This test will fail sometimes because of unlucky random numbers, but only
// very very rarely.
BOOST_CHECK(one > RAND_MAX || two > RAND_MAX);
}
BOOST_AUTO_TEST_CASE(wide_rand_excl_supports_larger_range)
{
const long long top = 1000 * (long long)RAND_MAX;
long long
one = wide_rand_excl<long long>(top),
two = wide_rand_excl<long long>(top);
BOOST_CHECK(one < top);
BOOST_CHECK(two < top);
// This test will fail sometimes because of unlucky random numbers, but only
// very very rarely.
BOOST_CHECK(one > RAND_MAX || two > RAND_MAX);
}
} // namespace } // namespace
} // namespace util } // namespace util