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

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

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

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

Ответ 1

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

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

Ответ 2

C++ 11

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

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

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

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

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

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

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

Ответ 3

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

Ответ 4

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

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

  • Метод центральной предельной теоремы
  • Преобразование Box-Muller
  • полярный метод Марсалья
  • Алгоритм Зиггурата
  • Метод выборки обратного преобразования.
  • cpp11random использует С++ 11 std::normal_distribution с std::minstd_rand (это фактически преобразование Box-Muller в clang).

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

normaldistf

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

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

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


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

Ответ 5

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

#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, превратите ряды в пропорции общего количества х, т.е. сколько образцов, получите z-значения и нарисовать их. Прямая линия вверх - это желаемый результат).

Ответ 6

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

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

Ответ 7

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

#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;

Ответ 10

Если вы используете С++ 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);

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

Ответ 11

Я следовал определению 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);
}

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

Ответ 12

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

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

Ответ 13

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

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

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

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

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

Ответ 14

Реализация Box-Muller:

#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;
}

Ответ 15

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

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

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

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

Ответ 16

Взгляните на то, что я нашел.

Эта библиотека использует алгоритм Зиггурата.

Ответ 17

Компьютер является детерминированным устройством. В расчетах нет случайности. Кроме того, арифметическое устройство в ЦП может оценивать суммирование по некоторому конечному набору целых чисел (выполняя оценку в конечном поле) и конечный набор действительных рациональных чисел. А также выполняются побитовые операции. Math заключает сделку с более большими наборами, такими как [0.0, 1.0] с бесконечным числом точек.

Вы можете прослушивать какой-нибудь провод внутри компьютера с каким-то контроллером, но будет ли он иметь одинаковые дистрибутивы? Я не знаю. Но если предположить, что это сигнал является результатом накопления значений огромного количества независимых случайных величин, тогда вы получите примерно нормальную распределенную случайную величину (это было доказано в теории вероятностей)

Существуют алгоритмы, называемые псевдослучайным генератором. Как я понял, целью псевдослучайного генератора является эмулирование случайности. И критерии доброты: - эмпирическое распределение сходится (в некотором смысле - точечно, равномерно, L2) до теоретического - значения, которые вы получаете от случайного генератора, кажутся идеальными. Конечно, это не верно с "реальной точки зрения", но мы предполагаем, что это правда.

Один из популярных методов - вы можете суммировать 12 irv с равномерными распределениями... Но, честно говоря, при выводе Центральной предельной теоремы с помо- щью преобразования Фурье, серии Тейлора, необходимо иметь n → + inf предположения Несколько раз. Итак, например, теоретически. Лично я не понимаю, как люди выполняют суммирование из 12 i.r.v. с равномерным распределением.

У меня была теория пробок в университете. И особенно для меня это просто математический вопрос. В университете я увидел следующую модель:


double generateUniform(double a, double b)
{
  return uniformGen.generateReal(a, b);
}

double generateRelei(double sigma)
{
  return sigma * sqrt(-2 * log(1.0 - uniformGen.generateReal(0.0, 1.0 -kEps)));
}
double generateNorm(double m, double sigma)
{
  double y2 = generateUniform(0.0, 2 * kPi);
  double y1 = generateRelei(1.0);
  double x1 = y1 * cos(y2);
  return sigma*x1 + m;
}

Такой способ, как сделать это, был просто примером, я предполагаю, что существуют другие способы его реализации.

Доказательство того, что это правильно, можно найти в этой книге "Москва, БМТУ, 2004: XVI Теория вероятностей, пример 6.12, с .246-247" Крищенко Александр Петрович ISBN 5-7038-2485 -0

К сожалению, я не знаю о существовании перевода этой книги на английский язык.