Random

Files CCore/inc/Random.h CCore/src/Random.cpp

Files CCore/inc/PlatformRandom.h

Random numbers have many use in applications. CCore provides a quality random number generator implementation, based on the Mersenne Twister algorithm.

There are several reasons why we use our own RNG. First, we need quality. Second, we need some properties from RNG: it must be lightweight and generate unique and non-periodical random number sequence. Finally, RNG must be an object, not a global function. Because it requires some state to work, using a global function introduces a global variable, the access to this variable must be synchronized in multithreaded applications. That implies an excessive bottleneck and a performance penalty. That is why we abstain from the using the standard C function rand().

The class Random is a random number generator. It contains inside the MT19937 Mersenne Twister state machine. To make the random sequence unique and non-periodical, the state is altered by the clock timer at the beginning and periodically during the random number generation process.


class Random : NoCopy
 {
   ....

  public:
  
   Random();
   
   void warp(PtrLen<const uint8> data);

   uint8  next8();
   
   uint16 next16();
   
   uint32 next32();
   
   uint64 next64(); 
    
   template <class UInt>
   UInt next_uint();
   
   uint32 select(uint32 lim) { return lim?uint32( next64()%lim ):next32(); }
   
   uint32 select(uint32 a,uint32 b) { return a+select(b-a+1); }
   
   template <class UInt>
   UInt select_uint(UInt lim);
 
   template <class UInt>
   UInt select_uint(UInt a,UInt b);

   template <class UInt>
   void fill(PtrLen<UInt> r); 
   
   template <class UInt>
   void fill(UInt *ptr,ulen len); 
 };

There is not destructor, only default constructor. Multiple Random objects generates different random numbers.

warp() alters the internal state. You can use this method to improve the random seeding using some entropy source to produce a block of bytes and supply it as the argument.

next8(), next16(), next32() and next64() return a random number of the given unsigned integral size defined type.

next_uint<UInt>() returns a random unsigned integer, the type is specified as a template argument.

fill() fills the given range of unsigned integers by random values.

select(uint32 lim) generates a random number in the range [0,lim), almost uniformly distributed. If the lim is zero, then the value range is all uint32 values. The non-uniformness is negligible for casual applications.

select(uint32 a,uint32 b) generates a random number in the range [a,b], almost uniformly distributed. a must be less or equal than b.

select_uint(UInt lim) is similar to select(uint32 lim), but works for the any unsigned integral type, specified as the template argument.

select_uint(UInt a,UInt b) is similar to select(uint32 a,uint32 b), but works for the any unsigned integral type, specified as the template argument.

RandomBase

Random is implemented with help of the RandomBase class.


template <class T>
class RandomBase : NoCopy
 {
  private:
 
   T & getObj() { return *static_cast<T *>(this); }
   
  public:
 
   template <class UInt>
   UInt next_uint();
 
   uint32 select(uint32 lim);
 
   uint32 select(uint32 a,uint32 b);
 
   template <class UInt>
   UInt select_uint(UInt lim);
 
   template <class UInt>
   UInt select_uint(UInt a,UInt b);
 
   template <class UInt>
   void fill(PtrLen r); 
 
   template <class UInt>
   void fill(UInt *ptr,ulen len) 
 };

You may use this class to build you custom random class using the following pattern:


class CustomRandom : public RandomBase<CustomRandom>
 {
  public:

   ....

   void warp(PtrLen<const uint8> data);

   uint8  next8();
   
   uint16 next16();
   
   uint32 next32();
   
   uint64 next64(); 
 };

You have to implement only four base methods and warp(). You may derive them from the one like this:


class CustomRandom : public RandomBase<CustomRandom>
 {
  public:

   ....

   using UnitType = uint32;

   uint32 next();

   void warp(PtrLen<const uint8> data);

   uint8  next8()  { return uint8 (next()); }
   
   uint16 next16() { return uint16(next()); }
   
   uint32 next32() { return uint32(next()); }
   
   uint64 next64() 
    {
     UIntSplit<uint64,uint32> split;
     
     split[0]=next32();
     split[1]=next32();
     
     return split.get(); 
    }
 };

Mersenne Twister

Mersenne Twister is a generic algorithm, MT19937 — it's particular version. Implementation of the Mersenne Twister is enclose in the namespace MersenneTwister.


struct MT19937
 {
  // unit
 
  using UnitType = uint32 ;
  
  static const ulen W = 32 ;
  
  // state
  
  static const ulen N = 624 ;
  static const ulen M = 397 ;
  static const ulen R =  31 ;
  
  static const UnitType A = 0x9908B0DF ;
  
  // output
  
  static const ulen S1 = 11 ;
  static const ulen S2 =  7 ;
  static const ulen S3 = 15 ;
  static const ulen S4 = 18 ;
  
  static const UnitType B = 0x9D2C5680 ;
  static const UnitType C = 0xEFC60000 ;
  
  // initialization
  
  static const ulen ReMessCount =  100 ;
  static const ulen MessCount   = 1000 ;
  
  static const UnitType HotBits[N];
 };

The structure MT19937 contains the MT19937 version parameters. It includes initial random state bits from HotBits.


template <class P>
class Gen : NoCopy
 {
  public:

   using UnitType = typename P::UnitType ; 
   
  private: 
  
   ulen remess_count;
   ulen ind;
   UnitType state[P::N];
   
  protected: 
  
   PtrLen<UnitType> getState() { return Range(state); }
   
   void hotbits();
   
   void messup();
   
   void pure_init();
   
   UnitType pure_next();
   
  public:
  
   UnitType next();

   void warp(PtrLen<const uint8> data);
 };

The class Gen is the generator itself. It is a Partial Class, parametrized by a parameter structure.

The only public method is the next(): it returns a next random number of the UnitType. But before using this class, its internal state must be initialized.

First, the buffer state must be initialized. You can do it in a derived class, getState() gives access to this buffer. The method hotbits() fills this buffer with some predefined random bits.

Second, use messup() method to "mess-up" the state and add to it some clock input. It makes the state unprobably repeatable. You may also use the method pure_init() to skip this mess and start the generator as the pure Mersenne Twister generator. In such case to continue use the method pure_next() for the random number generation. These "pure" methods are included for the testing purpose mostly.

PlatformRandom

The header PlatformRandom.h is a stub header. It contains the definition of the type PlatformRandom. The CCore header defines this type as Random. But target may define its own header file with the same name and override this stub. In such case the defined type must provide the same functionality as the type Random.

For example, the target WIN64 defines the type PlatformRandom as IntelRandom to utilize the Intel hardware RNG.