Nicht alle sind immer gleich

Zufallszahlen in C++ müssen nicht immer der Normalverteilung folgen. Gerade für Simulationen sind oft andere Verteilungen wesentlich wichtiger, weswegen die Standardbibliothek auch eine Sammlung der häufigsten mitbringt.

Video

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <random>
#include <utility>
#include <vector>
#include <fstream>

using namespace std;

int main()
{
    random_device dev;
    auto seed = dev();
    default_random_engine engine { seed };
    auto normal = normal_distribution<double>(50, 12.5);
    auto uniform = uniform_int_distribution<int>(0, 99);
    const int total_runs = 1000000;

    vector<pair<int, int>> result_bins(100);

    for(unsigned int i = 0; i < total_runs; i++)
    {
        auto normal_value = normal(engine);
        normal_value = static_cast<int>(min(max(normal_value, 0.0), 99.0));
        auto uniform_value = uniform(engine);

        ++(result_bins[normal_value].first);
        ++(result_bins[uniform_value].second);
    }

    ofstream output_file {"output.csv" };
    output_file << R"("","normal","uniform")";
    output_file << endl;
    int value = 0;
    for (auto amount : result_bins)
    {
        output_file << value << "," << amount.first << "," << amount.second << endl;
        ++value;
    }
    output_file.close();
}

Erklärung

Auf dem eigentlichen Zufallszahlengenerator (hier in Form der default_random_engine) sitzt eine Verteilung, die die erzeugten Zufallszahlen in die vom Nutzer gewünschte Form bringt. Das kann eine einfache Gleichverteilung in einem bestimmten Wertebereich sein, wie sie beispielsweise uniform_int_distribution bereitstellt. Es sind aber auch komplexere Verteilungen möglich, von denen die Standardbibliothek einige mitbringt.

Hier im Beispiel gezeigt ist die Normalverteilung mit ihrer bekannten Gausskurve. Die Normalverteilung hat zwei Parameter: die Mittelwert µ und die Standardabweichung σ. Ersterer gibt die Position der Glockenkurve der Verteilungsdichtefunktion an, letztere deren Breite (mathematisch exakt gesprochen ist σ; natürlich anders definiert). Beide Parameter kann man der normal_distribution übergeben und erhält dann Zufallszahlen in der Häufigkeit, wie sie die Dichtefunktion vorgibt. Hier im Beispiel bedeutet das zum Beispiel, dass ~68% aller Zufallszahlen im Bereich [37,5 , 62,5] liegen (µ ± σ). Die Zahlen weit entfernt vom Mittelwert kommen deutlich seltener vor.

Um die beiden Verteilungsdichtefunktionen zu visualisieren berechnet der Code im Beispiel ein Histogramm (letztlich bloß eine diskrete Variante der kontinuierlichen Verteilungsdichtefunktion). Dazu werden die generierten Zufallszahlen als Index in einen Vektor verwendet. Die Einträge des Vektor fungieren als Zähler, die die Häufigkeit ihres Indexes zählen (Zeilen 26 und 27 für jeweils die Normal- und die Gleichverteilung). Da die Normalverteilung eine unendliche Dichtefunktion hat, d.h. es können, wenn auch mit sehr geringer Wahrscheinlichkeit, beliebige Werte vom -∞ bis +∞ auftreten. Da wir unser Histogramm aber nur im Bereich von 0 bis 99 berechnen, müssen wir vorher noch auf diesen Bereich normieren. Dazu schneiden wie hier einfach den Wertebereich entsprechend ab und schlagen die entsprechenden Vorkommen außerhalb liegender Zahlen den Grenzen zu (Zeile 23). Für den Anwendungsfall hier reicht das. In realen Anwendungsfällen sollte man damit allerdings vorsichtig sein. Wie im Video zu sehen ist, verschieben wir dadurch die Wahrscheinlichkeiten. Die Grenzwerte kommen plötzlich mit zu hoher Wahrscheinlichkeit vor. Dieses Problem entsteht generell bei der Begrenzung von Verteilungen mit unendlicher Dichtefunktion auf einen endlichen Bereich (so zum Beispiel auch bei der Exponientialfunktion).

Die Zeilen 30 bis 39 schreiben schließlich die gezählten Häufigkeiten noch in eine CSV-Datei, die dann in einer handelsüblichen Tabellenkalkulation geöffnet und visualisiert werden kann. Im Ergebnis sehen wie die erwarteten Dichtefunktionen: eine waagerechte Linie für die Gleichverteilung und die bekannte Gausskurve der Normalverteilung.