2013年2月14日 星期四

C++ 程式設計筆記 - 奇點亂數產生器[原創]

之前因為一些緣由需要設計一個亂數產生器,原本使用線性同餘法設計,線性同餘法的亂數產生器可由如下程式碼即可產生:

Seed = (Seed * Multiplier + Increment) % Modulos;

但測試後發現這樣產生的亂數有個非常大的問題就是產生的 Seed 如果曾經出現過那麼之後的亂數就會完全重複而產生循環,於是決定設計一個改良的亂數產生器,實作的程式碼如下:

GravitationalSingularityRandom.h :

class GravitationalSingularityRandom
{
private:
  long Seed;
  long Multiplier;
  long SourceIncrement;
  long Modulos;
  long TargetIndex;
  long TargetIncrement;
  unsigned char BufferRandom[256];
public:
  GravitationalSingularityRandom(long RandomSeed = 0);
  ~GravitationalSingularityRandom();
  void Random(unsigned char * Buffer, long Size);
};


GravitationalSingularityRandom.cpp :

#include "GravitationalSingularityRandom.h"

GravitationalSingularityRandom::GravitationalSingularityRandom(long RandomSeed)
{
  long i;
  Seed =  RandomSeed;
  Multiplier = 2593;
  SourceIncrement =  3079;
  TargetIncrement = 17;
  Modulos = 65537;
  TargetIndex = 0;
  for (i = 0; i < 256; i++)
  {
    BufferRandom[i] = (unsigned char) i;
  }
}

GravitationalSingularityRandom::~GravitationalSingularityRandom()
{
}

void GravitationalSingularityRandom::Random(unsigned char * Buffer, long BufferSize)
{
  int i;
  long SourceIndex = 0;
  unsigned char RandomMoving = 0;
  for (i = 0; i < BufferSize; i ++)
  {
    Seed = (Seed * Multiplier + SourceIncrement) % Modulos;
    SourceIndex = (Seed & 0xFF);
    TargetIndex = ((TargetIndex + TargetIncrement) & 0xFF);
    RandomMoving = BufferRandom[SourceIndex];
    BufferRandom[SourceIndex] = BufferRandom [TargetIndex];
    BufferRandom[TargetIndex] = RandomMoving;
    Buffer[i] = RandomMoving;
  }
}


一開始在建構式中產生一個大小為 0 - 255 的表格並在其中放入 0 - 255 的數字當作索引。程式碼中 Seed 、 Multiplier 、 SourceIncrement 跟 Modulos 都是用來產生線性同餘法的亂數所需的參數,但是卻不能將之直接拿來使用,而是拿來當表格的互換索引,也就是說線性同餘法所產生的亂數不是拿來直接使用而是用做此演算法所需的奇點,這點有點像是洗牌法但不同的是洗牌法是要產生連續不重複亂數,而這個作法是為了要避免亂數進入循環,並且產生的數值愈多接下來產生數值的複雜度也就愈高,經測試這個亂數產生器能產生非常接近自然亂數的數值。