it-swarm.com.ru

Генерация случайных чисел после нормального распределения в C/C++

Как я могу легко генерировать случайные числа после нормального распределения в C или C++?

Я не хочу использовать Boost.

Я знаю, что Кнут подробно об этом говорит, но сейчас у меня нет его книг.

107
Damien

Существует много способов генерировать числа с гауссовым распределением из обычного ГСЧ .

Преобразование Бокса-Мюллера обычно используется. Он правильно выдает значения с нормальным распределением. Математика это легко. Вы генерируете два (равномерных) случайных числа, и, применяя к ним формулу, вы получаете два нормально распределенных случайных числа. Верните одно и сохраните другое для следующего запроса случайного числа.

87
S.Lott

C++ 11

C++ 11 предлагает std::normal_distribution , как я бы сказал сегодня.

C или старше C++

Вот несколько решений в порядке возрастания сложности:

  1. Добавьте 12 одинаковых случайных чисел от 0 до 1 и вычтите 6. Это будет соответствовать среднему и стандартному отклонению нормальной переменной. Очевидным недостатком является то, что диапазон ограничен ± 6 - в отличие от истинного нормального распределения.

  2. Преобразование Бокса-Мюллера. Это перечислено выше, и его относительно просто реализовать. Однако если вам нужны очень точные выборки, имейте в виду, что преобразование Бокса-Мюллера в сочетании с некоторыми равномерными генераторами страдает аномалией, называемой эффектом Нейва.1,.

  3. Для лучшей точности я предлагаю рисовать форму и применять обратное кумулятивное нормальное распределение, чтобы получить нормально распределенные переменные. Здесь очень хороший алгоритм для обратных кумулятивных нормальных распределений.

1. Х. Р. Нив, "Об использовании преобразования Бокса-Мюллера с мультипликативными конгруэнтными генераторами псевдослучайных чисел", Прикладная статистика, 22, 92-97, 1973.

44
Peter G.

Быстрый и простой метод - просто сложить количество равномерно распределенных случайных чисел и взять их среднее значение. Смотрите Центральная предельная теорема для полного объяснения того, почему это работает.

31
Paul R

Я создал C++ проект с открытым исходным кодом для нормально распределенного теста генерации случайных чисел

Он сравнивает несколько алгоритмов, в том числе

  • Метод центральной предельной теоремы
  • Преобразование Бокса-Мюллера
  • Марсалья полярный метод
  • Алгоритм зиккурата
  • Метод выборки обратного преобразования.
  • cpp11random использует C++ 11 std::normal_distribution с std::minstd_Rand (на самом деле это преобразование Бокса-Мюллера в clang).

Результаты версии с одинарной точностью (float) на iMac [email protected], clang 6.1, 64-bit: 

normaldistf

Для правильности программа проверяет среднее значение, стандартное отклонение, асимметрию и эксцесс образцов. Было обнаружено, что метод CLT путем суммирования 4, 8 или 16 одинаковых чисел не имеет хорошего эксцесс, как другие методы.

Алгоритм Ziggurat имеет лучшую производительность, чем другие. Тем не менее, он не подходит для SIMD-параллелизма, так как требует поиска таблицы и ответвлений. Box-Muller с набором инструкций SSE2/AVX намного быстрее (x1,79, x2,99), чем не-SIMD версия алгоритма зиккурата.

Поэтому я предложу использовать Box-Muller для архитектуры с наборами команд SIMD, и в противном случае может быть ziggurat.


Постскриптум тест использует простейший LCG PRNG для генерации равномерно распределенных случайных чисел. Так что это может быть недостаточно для некоторых приложений. Но сравнение производительности должно быть справедливым, поскольку во всех реализациях используется один и тот же PRNG, поэтому тест в основном тестирует производительность преобразования.

23
Milo Yip

Вот пример C++, основанный на некоторых ссылках. Это быстро и грязно, лучше не изобретать заново и не использовать библиотеку наддува.

#include "math.h" // for Rand, and Rand
double sampleNormal() {
    double u = ((double) Rand() / (Rand_MAX)) * 2 - 1;
    double v = ((double) Rand() / (Rand_MAX)) * 2 - 1;
    double r = u * u + v * v;
    if (r == 0 || r > 1) return sampleNormal();
    double c = sqrt(-2 * log(r) / r);
    return u * c;
}

Вы можете использовать график QQ, чтобы изучить результаты и посмотреть, насколько хорошо он приближается к реальному нормальному распределению (ранжируйте ваши выборки 1..x, превратите ранги в пропорции общего количества x, т.е. сколько образцов, получите значения z и нарисуйте их. Восходящая прямая линия - желаемый результат).

14
Pete855217

Используйте std::tr1::normal_distribution.

Пространство имен std :: tr1 не является частью boost. Это пространство имен, которое содержит дополнения к библиотекам из Технического отчета C++ 1 и доступно в современных компиляторах Microsoft и gcc, независимо от boost.

12
Joe Gauterin

Вот как вы генерируете образцы на современном компиляторе C++.

#include <random>
...
std::mt19937 generator;
double mean = 0.0;
double stddev  = 1.0;
std::normal_distribution<double> normal(mean, stddev);
cerr << "Normal: " << normal(generator) << endl;
12
Petter

Вы можете использовать GSL . Некоторые полные примеры приведены , чтобы продемонстрировать, как его использовать.

4
Denis Arnaud

Посмотрите на: http://www.cplusplus.com/reference/random/normal_distribution/ . Это самый простой способ создания нормальных дистрибутивов.

4
telcom

Если вы используете C++ 11, вы можете использовать std::normal_distribution :

#include <random>

std::default_random_engine generator;
std::normal_distribution<double> distribution(/*mean=*/0.0, /*stddev=*/1.0);

double randomNumber = distribution(generator);

Есть много других распределений, которые вы можете использовать для преобразования выходных данных механизма случайных чисел.

4
Drew Noakes

Я следовал определению PDF, приведенному в http://www.mathworks.com/help/stats/normal-distribution.html , и придумал следующее:

const double DBL_EPS_COMP = 1 - DBL_EPSILON; // DBL_EPSILON is defined in <limits.h>.
inline double RandU() {
    return DBL_EPSILON + ((double) Rand()/Rand_MAX);
}
inline double RandN2(double mu, double sigma) {
    return mu + (Rand()%2 ? -1.0 : 1.0)*sigma*pow(-log(DBL_EPS_COMP*RandU()), 0.5);
}
inline double RandN() {
    return RandN2(0, 1.0);
}

Возможно, это не лучший подход, но он довольно прост.

3
MJVC

Реализация Бокса-Мюллера:

#include <cstdlib>
#include <cmath>
#include <ctime>
#include <iostream>
using namespace std;
 // return a uniformly distributed random number
double RandomGenerator()
{
  return ( (double)(Rand()) + 1. )/( (double)(Rand_MAX) + 1. );
}
 // return a normally distributed random number
double normalRandom()
{
  double y1=RandomGenerator();
  double y2=RandomGenerator();
  return cos(2*3.14*y2)*sqrt(-2.*log(y1));
}

int main(){
double sigma = 82.;
double Mi = 40.;
  for(int i=0;i<100;i++){
double x = normalRandom()*sigma+Mi;
    cout << " x = " << x << endl;
  }
  return 0;
}
0
Gall Anonim

1) Графически интуитивно понятный способ генерации гауссовских случайных чисел с помощью чего-то похожего на метод Монте-Карло. Вы бы сгенерировали случайную точку в рамке вокруг кривой Гаусса, используя свой генератор псевдослучайных чисел в C. Вы можете вычислить, находится ли эта точка внутри или ниже гауссовского распределения, используя уравнение распределения. Если эта точка находится внутри гауссовского распределения, то в качестве значения x этой точки вы получите гауссово случайное число. 

Этот метод не идеален, потому что технически кривая Гаусса идет к бесконечности, и вы не можете создать прямоугольник, который приближается к бесконечности в измерении x. Но кривая Guassian приближается к 0 в измерении y довольно быстро, так что я бы не беспокоился об этом. Ограничение размера ваших переменных в C может быть более ограничивающим фактором для вашей точности.

2) Другой способ - использовать центральную предельную теорему, которая гласит, что при добавлении независимых случайных величин они образуют нормальное распределение. Помня эту теорему, вы можете аппроксимировать гауссово случайное число, добавив большое количество независимых случайных величин. 

Эти методы не самые практичные, но этого следует ожидать, когда вы не хотите использовать ранее существующую библиотеку. Имейте в виду, что этот ответ исходит от кого-то с небольшим или без опыта исчисления или статистики.

0
dan dan

Существуют различные алгоритмы обратного кумулятивного нормального распределения. Самые популярные в количественном финансировании тестируются на http://chasethedevil.github.io/post/monte-carlo--inverse-cumulative-normal-distribution/

На мой взгляд, нет особого стимула использовать что-то еще, кроме алгоритма AS241 от Wichura : это точность машины, надежность и скорость. Узкие места редко возникают при генерации случайных чисел по Гауссу.

Кроме того, это показывает недостаток зиккуратских подходов.

Главный ответ, который здесь выступает за Box-Müller, вы должны знать, что у него есть известные недостатки. Я цитирую https://www.sciencedirect.com/science/article/pii/S0895717710005935 :

в литературе Бокса-Мюллера иногда считают слегка неполноценным, в основном по двум причинам. Во-первых, если применить метод Бокса-Мюллера к числам из плохого линейного конгруэнтного генератора, преобразованные числа обеспечивают крайне плохое покрытие пространства. Графики преобразованных чисел со спиральными хвостами можно найти во многих книгах, особенно в классической книге Рипли, который, вероятно, был первым, кто сделал это наблюдение "

0
jherek

Список comp.lang.c FAQ имеет три различных способа легко генерировать случайные числа с гауссовым распределением.

Вы можете взглянуть на это: http://c-faq.com/lib/gaussian.html

0
Delgan