rand&co. (geteilt aus: Frage zu lvalue / rvalue Referenzen)



  • Ich nimm <random> und uniform_int_distribution. Ich will mir echt nicht Gedanken darüber machen müssen. Ich schreib ja nur kleine Spiele damit.



  • Timon Paßlick schrieb:

    Ist wegshiften eigentlich verändern oder löschen? Noch nie was davon gehört.

    Mit Wegshiften meine ich einen "Rechts-Shift" um ein paar Bits, also sowas wie "v = v >> 3". Rechtsverschiebung um N Bits entspricht bei "unsigned" Typen einer Division durch 2^N, also "v = v >> 3" macht das selbe wie "v = v / 8". Shiften geht allerdings viel schneller als Dividieren. Natürlich ist der Compiler schlau genug das zu optimieren wenn da wirklich durch die Konstante 8 dividiert wird. Aber die Schreibweise "v = v >> 3" bringt IMO auch deutlicher zum Ausdruck dass man die untersten drei Bits komplett verwirft als "v = v / 8".

    Timon Paßlick schrieb:

    Und was ist ein LCG Generator?

    LCG = Linear Congruential Generator
    Die Art von Pseudozufallsgenerator wo man
    X_neu = (X * A + B) modulo C
    rechnet.
    Mit A, B, C Konstanten. Und der "modulo C" Teil fehlt oft, da man C oft z.B. als 2^32 oder 2^64 wählt, und sich die "modulo C" Operation dann automatisch durch die begrenzte Registerbreite (Variablengrösse) ergibt.

    Siehe https://en.wikipedia.org/wiki/Linear_congruential_generator



  • Ok, danke.



  • Vielleicht noch ergänzend: wenn du einen LCG-Generator in C++ benutzen willst, kannst du nehmen:
    std::linear_congruential_engine

    Wichtig ist, dass bei rand() folgendes gilt (http://en.cppreference.com/w/cpp/numeric/random/rand)

    There are no guarantees as to the quality of the random sequence produced. In the past, some implementations of rand() have had serious shortcomings in the randomness, distribution and period of the sequence produced (in one well-known example, the low-order bit simply alternated between 1 and 0 between calls).

    Daher: wenn du in C++ den RNG direkt angibst, dann gibt es Garantien über die Güte. Bei rand() dagegen garantiert dir der Standard nix. Wobei für kleine Spielchen natürlich trotzdem rand + modulo absolut ausreichen kann, sofern das rand() halbwegs ordentlich läuft. Außerdem habe ich das rand-considered-harmful-Video hier noch gar nicht gepostet (finde ich sehr lustig anzuschauen, besonders den Anfang): https://www.youtube.com/watch?v=LDPMpc-ENqY



  • Was von dem ganzen Zeug, was ihr mir hier gezeigt habt, braucht am wenigsten Aufwand zum Einarbeiten?
    Das Video ist übrigens gut.



  • Am wenigsten Aufwand zum Einarbeiten ist rand().
    Oder du guckst auf die Wikipediaseite zu XorShift und kopierst dir den Generator raus. Der liefert auch sehr vernünftige Sequenzen. (Nicht perfekt, aber meist ausreichend und deutlich besser als LCGs.)



  • Ich habe eine einsteigerfreundliche und trotzdem moderne Zufallsgeneratorklasse entwickelt (mit der Hilfe von Beispielcode in "The C++ Programming Language").
    Falls ihr sie nutzen möchtet, sind hier die .h und .cpp Dateien:

    Zufallsgenerator.h

    #pragma once
    class Zufallsgenerator
    {
    public:
    	Zufallsgenerator() = delete; //würde keinen Sinn ergeben
    	Zufallsgenerator(const int &reichweite); //Reichw. von 0 bis Argument
    	Zufallsgenerator(const int &low, const int &high); // Reichw. von low bis high
    	int operator()(); //gibt Zufallszahl zurück
    private:
    	std::default_random_engine e;
    	std::uniform_int_distribution<> distr;
    };
    

    Zufallsgenerator.cpp

    //stdafx.h löschen, wenn ihr kein Visual Studio nutzt
    #include "stdafx.h"
    #include "Zufallsgenerator.h"
    
    Zufallsgenerator::Zufallsgenerator(const int &reichweite) :distr{0, reichweite} { }
    
    Zufallsgenerator::Zufallsgenerator(const int & low, const int & high) : distr{low, high} { }
    
    int Zufallsgenerator::operator()() { return distr(e); }
    

    Hoffe, mein Code gefällt euch.
    Ihr könnt auch gerne kritisches Feedback geben, ich guck dann, was ich tuen kann.



  • Ich sehe irgendwie den Vorteil nicht, deine Klasse zu verwenden. Die nimmt mir keine Arbeit ab, da kann man auch direkt die entsprechenden std Funktionen verwenden.



  • Das kann man halt als Anfänger gut nutzen. Die Implementierung ist ja hinter der Schnittstelle und man braucht sie nicht zu verstehen.
    <random> ist unverkleidet ungeschickt für Anfänger, das gibt ja sogar Stroustrup zu.
    Du solltest das nicht als Feature, sondern als zero-cost Abstraktion sehen, die hässliche Details verbirgt.



  • Ich bin auch kein Fan von deiner Klasse.
    Einerseits macht sie die schöne Aufteilung in Generatoren und Verteilungen von <random> wieder zunichte, andererseits fehlen fundamentale Operationen wie Seeding oder Serialisierung.
    Ferner sehe ich nicht, wozu man hier std::default_random_engine nutzen sollte. Plattform-unabhängige Zufallszahlen sind in vielen Anwendungen sehr erwünscht.

    LG



  • Verstehe, was du meinst, muss man halt abwägen.
    Das ist Stroustrups Beispielcode, von dem sich meiner ableitet:

    class Rand_int {
    public:
        Rand_int(int low, int high) :dist{low,high} { }
        int operator()() { return dist(re); } // einen int ziehen
    private:
        default_random_engine re;
        uniform_int_distribution<> dist;
    };
    

    Deshalb auch die Wahl der Engine.

    Edit: Was Seeding oder Serialisierung ist, wurde im Buch noch nicht erklärt, deshalb ist es auch nicht eingebaut. Da kommen aber trotzdem Zufallszahlen raus, auch wenn ich nichts von Beidem tue.



  • Und warum ist <random> ungeschickt?

    Ich bin auch nicht der Meinung, dass dein Code Schnipsel Anfängerfreundlich ist. Es fehlen Header und der ist Compiler spezifisch. Vorkompilierte Header würde ich Anfängern nicht empfehlen, denn um das zu verstehen muss man den Buildprozess schon halbwegs verstanden haben.

    Außerdem gefällt mir der Mischmasch aus deutschen und englischen Bezeichnern nicht.

    Es ist insgesamt ein einaches Beispiel von Stroustrup wie man einen gleichverteilten ganzzahlen Zufallszahlen generator erstellen kann. Zur Übung und zum Lernen ist das alles sicher in Ordnung, aber ich würde Anfängern raten, es nicht einfach zu kopieren und zu benutzen, sondern sich mit dem Thema Zufallszahlen selbst zu beschäftigen.



  • Ich seh's ein, der Code ist wohl eher speziell für mich eine gute Übergangslösung (bis ich zum entsprechenden Kapitel komme).
    Trotzdem glaube ich nicht, dass es einen Anfänger juckt, ob die Details plattformabhängig sind. Eher Fortgeschrittene. Mir z.B. ist es ehrlich gesagt völlig schnuppe.



  • Naja, ein Anfänger möchte Code haben, der aus jeden Fall funktioniert. Nicht Code, der nur unter bestimmten Bedingungen funktioniert. Du bindest ja zum Beispiel nicht <random> ein, daher finde ich es eh erstaunlich, dass dein Code überhaupt kompiliert.

    Wird dann vermutlich mit Visual Studio zusammenhängen?

    Nächste Sache: warum werden bei dir die int-Variablen als const int& übergeben? In Stroustrups Beispiel ist das nicht so.

    Was man für Anfänger überlegen könnte, wäre sowas:

    class EasyRandom {
    public:
      EasyRandom();
      int uniform_int(int min, int max);
      double uniform_double(double min, double max);
    private:
      std::mt19937 mt;
    };
    

    Das spiegelt auch wieder, dass die uniform_xxx_distribution schnell erstellt werden kann, während der Generator-Constuctor selbst teuer ist.

    Andererseits: wenn man sich den Random-Header einmal etwas näher anschaut, sind Generatoren/Verteilungen einfach zu benutzen. Das einzig komplizierte ist ordentliches Seeden. Ich habe noch nicht rausgefunden, wie viel/was ich mindestens in eine seed_seq tun muss, um z.B. den mt19937-Initialstate vollständig zufällig hinzubekommen.



  • Ja, hatte vergessen, dass <random> im vorkompilierten Header ist.
    Die Argumente werden nicht verändert und müssen nicht kopiert werden. Deshalb const int&.
    Deinen Beispielcode versteh ich nicht, wohl eher nicht so geeignet 😞



  • Timon Paßlick schrieb:

    Die Argumente werden nicht verändert und müssen nicht kopiert werden. Deshalb const int&.

    Kopieren ist hier aber mindestens so schnell wie die zusätzliche Indirektion. Daher nehme ich fundamentale Typen prinzipiell immer by Value, nicht by Const-Reference.

    Timon Paßlick schrieb:

    Deinen Beispielcode versteh ich nicht, wohl eher nicht so geeignet 😞

    Du hättest stattdessen sagen können, was du daran nicht verstehst, damit dir geholfen werden kann. Ich sehe nicht ganz, wie du deinen Code aber nicht wob's Code verstehen kannst.

    LG



  • Was machen die 2 Funktionen?



  • Timon Paßlick schrieb:

    Was machen die 2 Funktionen?

    Die erste soll einen gleichverteilten int im angegebenen Bereich liefern, die zweite einen gleichverteilten double.

    Vielleicht hätte ich die besser generate_uniform_int bzw. generate_uniform_double genannt.

    Man könnte dann z.B. double generate_gaus(double mean, double sigma) hinzufügen usw. Hebt man halt die Trennung von Generator und Distributionen ein bisschen auf, dafür bekommt man Syntax-Vervollständigung von der IDE, wenn man den "my_rand_object." tippt.

    Dann braucht man nämlich nur ein einfaches "Zufallsobjekt" und kann mit diesem beliebige Ranges an Zufallszahlen berechnen. Es erschien mit wenig sinnvoll, die distribution mit in das Objekt aufzunehmen*. Aber wie gesagt, wir sind dann dicht am Original dran. Lediglich das Initialisieren hätte man dann schön versteckt.

    * ich habe sogar mal einen Benchmark gesehen, wo es besser war, die distibution innerhalb einer Schleife zu erzeugen als außerhalb - offenbar hat der Optimizer das dann besser optimieren können. Daher keine Angst vorm Erzeugen der distribution-Objekte.



  • Ich finde das immer interessant, wie komisch das ist mit den Compilern. Die können immer andere Dinge gut optimieren, als man denkt. Mir wurde das z.B. so erklärt:
    Muss nicht kopiert werden -> Referenz setzen.
    Konstruktoren selten aufrufen.
    Ich bin richtig verwirrt. Muss man Assembler können und die Compiler haargenau kennen, um schnellen Code zu schreiben?
    Apropos: Warum überlegt der Compiler eigentlich nur, ob er Funktionen in einfachen Code umschreiben kann, wenn inline davorsteht? Und sollte ich vor jede Funktion inline schreiben?
    Edit: Ich weiß, das ist wieder ein anderes Thema, aber der Thread ist sowieso schon abgeschwiffen. 🙂



  • Timon Paßlick schrieb:

    Warum überlegt der Compiler eigentlich nur, ob er Funktionen in einfachen Code umschreiben kann, wenn inline davorsteht?

    Deine Annahme stimmt nicht.

    Siehe z.B. http://en.cppreference.com/w/cpp/language/inline
    Lies insbesondere den Text ab "The original intent"...

    Ähnlich wie das "register"-Keyword. Das nun in C++17 erstmal ganz ungenutzt ist.


Anmelden zum Antworten