четверг, 9 февраля 2012 г.

Свой ГСЧ

Стандартный генератор случайных чисел (ГСЧ) в C++ - функция rand(). Он работает по так-называемому конгруэнтному линейному алгоритму. Для инициализации генератора используется srand(seed).
У этого генератора есть несколько неприятных особенностей.
1. Он более-менее сносно работает, когда в своей программе вы генерируете небольшое количество независимых между собой случайных величин. Но когда с помощью одного и того же датчика генерируется с десяток различных переменных (A, B, C, D, E, F, G, H, I, J), то последовательность псевдослучайных значений, например, переменной A: A1, A2, A3, A4, A5, ... уже не будет выглядеть как случайная.
2. Требуется производить инициализацию счетчика с помощью функции srand(seed) в каждом потоке, в котором вызывается rand().
В общем, столкнувшись на практике со странностями работы стандартного ГСЧ, я решил написать свой. Основные требования - скорость работы и возможность обращаться к одному и тому же ГСЧ из разных потоков. И вот, что у меня получилось:


class CRnd
{
  private:
    unsigned long m_iran;
  public:
    void Init(unsigned long seed);
    unsigned long Rand();
}

void CRnd::Init(unsigned long seed)
{
  m_iran = seed;
}

unsigned long CRnd::Rand()
{
  unsigned long old_iran = m_iran;
  unsigned long new_iran, cur_iran;
  for (;;)
  {
    new_iran = 1664525L*old_iran+1013904223L;
    cur_iran = InterlockedCompareExchange((volatile long*)&m_iran, new_iran, old_iran);
    if (cur_iran == old_iran) break;
    old_iran = cur_iran;
  }
  return new_iran;
}

UPD. Множитель a = 1664525 и слагаемое c = 1013904223 были взяты из статьи в вики. На практике, однако, выяснилось, что получаемая последовательность чисел имеет короткий период, поэтому имеет смысл использовать другие параметры:
a = 1103515245, c = 12345, m = 2^31.

unsigned long CRnd::Rand()
{
  unsigned long old_iran = m_iran;
  unsigned long new_iran, cur_iran;
  for (;;)
  {
    new_iran = 1103515245L*old_iran+12345L;
    cur_iran = InterlockedCompareExchange((volatile long*)&m_iran, new_iran, old_iran);
    if (cur_iran == old_iran) break;
    old_iran = cur_iran;
  }
  return ((new_iran >> 16) & 32767L);
}

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Комментариев нет:

Отправить комментарий