
W poprzednim odcinku otrzymaliśmy całkiem przyzwoity generator liczb o rozkładzie równomiernym. Teraz uzupełnimy naszą klasę o metodę do pobierania wartości z rozkładu normalnego, zwanego też rozkładem Gaussa.
Zastosowanym algorytmem do generowania liczb losowych z rozkładu normalnego jest implementacja metody Kindermana i Monahana, zwanej ROU (ratio-of-uniforms method). Szczegóły tej metody można znaleźć w książce Wieczorkowskiego i Zielińskiego[2]
Moja implementacja w języku C++ zawarta jest w metodzie getFromNormalDistribution()
klasy RandomNumberGenerator
i wygląda następująco:
double RandomNumberGenerator::getFromNormalDistribution() { bool ok = false; double limit = sqrt(2/2.718281828); double X; do { // generuj U o rozkładzie równomiernym U(0,1) double U = getFromUniformDistribution(); // generuj V o rozkładzie równomiernym // U(-sqrt(2/e),sqrt(2/e)) double V = 2 * limit * getFromUniformDistribution() - limit; X = V / U; if (X*X <= 2*(3-U*(4+U))) { ok = true; } else if (X*X <= 2/U - 2*U) { if (X*X <= -4*log(U)) { ok = true; } } } while (!ok); return X; }
Jak widać używa on metody getFromUniformDistribution()
zdefiniowanej wcześniej a zwracającej nam dwie liczby z rozkładu równomiernego, jedna z przedziału (0,1) a druga z (-sqrt(2/e), sqrt(2/e)).
Ok. Teraz wypadałoby jakoś przetestować nasz generator. Do tego celu napiszemy szybko programik generujący histogram otrzymywanych wartości. Oto on:
#include <iostream> #include <vector> #include <math.h> #include "source/RandomNumberGenerator.cpp" using namespace std; int main(int argc, char* args[]) { // wektor przechowywujący liczebność wylosowanych liczb w danym przedziale vector<double> histogram; double min = -3; // dolna granica double max = 3; // górna granica double n = 1000; // liczba przedziałów // krok o jaki będą zwiększać się kolejne przedziały double step = (max - min) / n; // zainicjowanie licznikow for (int i = 0; i < n; i++) { histogram.push_back(0); } // losowanie i określenie w którym przedziale znajduje się wylosowana liczba for (int k = 0; k < 10000000; k++) { // pobranie wartości losowej double X = RandomNumberGenerator::Instance()->getFromUniformDistribution(); // 'odległość' od dolnej granicy double lX = X - min; if ((X >= min) && (X <= max)) { int i = (int) floor(lX / step); // obliczenie, do którego przedziału zalicza się wylosowana wartość histogram[i] = histogram[i] + 1; // zwiększenie licznika danego przedziału } } // wyprowadzenie wartości liczników na standardowe wyjście for (unsigned int j = 0; j < histogram.size(); j++) { cout << histogram[j] << endl; } return EXIT_SUCCESS; }
Przekierowując dane ze standardowego wyjścia na plik, można zapisać otrzymane wyniki:
$ ./RandomGen > stats.txt
Teraz statystyki można wsadzić np. do OpenOffice’a i wygenerować histogramy:
10 tys. wylosowanych liczb
100 tys. wylosowanych liczb
10 mln. wylosowanych liczb
Jak widzimy, im więcej wylosujemy liczb, tym histogram coraz bardziej przypomina krzywą Gaussa. Mamy więc dowód, że generator działa i otrzymujemy liczby z rozkładu normalnego. W następnym odcinku zajmiemy się rozkładem Cauchy’ego.
Podziel się:





