srand() und rand() auslagern



  • Hi, dieses Programm soll eine Zufallszahl berechnen, wobei der
    Benutzer die Grenzen eingeben kann. Es funktioniert, wenn alles
    in der main Funktion steht. Wenn ich jedoch einen Teil in eine Klasse
    auslagern möchte, kommt die Fehlermeldung:
    Verweis auf nicht aufgelöstes externes Symbol ""private: int__thiscall zufallszahl::rand(void)"
    ?rand@zufallszahl@AAEHXZ...

    zufallszahl.h:

    #ifndef ZUFALLSZAHL_H
    #define ZUFALLSZAHL_H
    
    
    class zufallszahl
    {
    public:
        zufallszahl();
        void definemo(int o);
        void definemu(int u);
        int showmo();
        int showmu();
        int berechne();
    
    private:
        int mo;
        int mu;
        int srand(int x);       //Diese Funktion macht Probleme
        int rand();             //Diese Funktion macht Probleme
    };
    
    #endif // ZUFALLSZAHL_H
    

    zufallszahl.cpp:

    #include "zufallszahl.h"
    #include <ctime>
    
    zufallszahl::zufallszahl()
    {
    
    }
    
    void zufallszahl::definemo(int o){
        this->mo = o;
    }
    
    void zufallszahl::definemu(int u){
        this->mu = u;
    }
    
    int zufallszahl::showmo(){
        return this->mo;
    }
    
    int zufallszahl::showmu(){
        return this->mu;
    }
    
    int zufallszahl::berechne(){        //Diese Funktion macht Probleme
        srand(time(NULL));
        return rand() % (mo-mu)+mu;
    }
    

    main.cpp:

    #include <iostream>
    #include "zufallszahl.h"
    
    using namespace std;
    
    int main()
    {
        zufallszahl z;
        int io, iu;
    
        cout<<"Obere Granze: ";
        cin>>io;
    
        cout<<"Untere Grenze: ";
        cin>>iu;
    
        z.definemo(io);
        z.definemu(iu);
    
        cout<<"Grenzen: "<<z.showmo()<<" "<<z.showmu()<<endl;
    
        cout<<"Die Zufallszahl ist: ";
        cout<<z.berechne();
    
        return 0;
    }
    


  • srand() und rand() sind Funktionen der Standardlibrary und in <cstdlib> deklariert. Du brauchst sie also nicht in deiner Klasse deklarieren:

    @theAnfänger77 sagte in srand() und rand() auslagern:

    int srand(int x);       //Diese Funktion macht Probleme
    int rand();             //Diese Funktion macht Probleme
    

    Hier solltest Du std::rand() aufrufen statt rand():

    @theAnfänger77 sagte in srand() und rand() auslagern:

    int zufallszahl::berechne() {        //Diese Funktion macht Probleme
        srand(time(NULL));
        return rand() % (mo-mu)+mu;
    }
    

    ... und std::srand(), das den Zufallszahlengenerator initialisiert (seeded) nur einmal zu beginn deines Programms. Von mir aus in Konstruktor von zufallszahl.

    Auch solltest du dir ein wenig ausdrucksvollere Variablenamen überlegen.



  • 🙂 Super!
    Vielen Dank für die Antwort!



    • rand() ist vollkommen veraltet, das wird in C genutzt, aber nicht in C++. Nutze stattdessen die Funktionen des Headers <random>. Wenn du doch einmal (in C) rand() benutzen solltest: srand() wird nur einmal im Programm (üblicherweise am Anfang) aufgerufen.
    • lass den leeren ctor weg, das ist überflüssig und schlechter Stil. Wenn der ctor nichts macht, musst du ihn auch nicht hinschreiben. Wenn du ihn unbedingt im Code haben willst, nutze zumindest zufallszahl() = default;. (wenn er leer ist; für <random> brauchst ihn ggf. um die member zu initialisieren)
    • Was soll mo sein? Was soll mu sein? Man kann aus dem Kontext erahnen, dass das eine wohl für "oben" und das andere für "unten" stehen soll; eindeutig ist das aber nicht. Gute Namen sind wichtig! Hier wären z.B. max und min aussagekräftiger.
    • Bei zusammengesetzten Namen gibt es verschiedene Möglichkeiten, das darzustellen. Üblich sind z.B. Unterstriche define_mo oder camelCase defineMo. Gerade bei komplexeren Namen ist es sonst unnötig schwer, das zu entziffern.
    • Es ist üblich, eigene Klassennamen mit einem Großbuchstaben zu beginnen (Zufallszahl)
    • Versuche, deinen Code einsprachig zu halten - Deutsch oder Englisch. Warum define/show? Der Rest ist doch auch Deutsch. Oder mach alles auf Englisch, vermutlich fast sogar noch besser.
    • definemx und showmx klingt sehr merkwürdig. Statt define würde ich lieber set_ verwenden und show klingt so, als würdest du etwas anzeigen. Stattdessen, gibst du aber etwas zurück, da ist get_ besser. Das sind auch die beiden üblichen Namen für solche getter-/setter-Funktionen.
    • Insgesamt ist die gesamte Aufgabe allerdings noch einmal zu überdenken. Das ganze manuell mit std::mt19937 zu machen ist vermutlich einfacher.

    Hier ein Beispiel:

    #ifndef RANDOMNUMBER_H
    #define RANDOMNUMBER_H
    
    #include <random>
    
    class Random_Number
    {
    public:
        Random_Number(int max, int min)
    	    : dist(min, max), mt(std::random_device{}()) {	}
        void set_max(int new_max) { dist.param(decltype(dist.param()){dist.min(), new_max}); }
        void set_min(int new_min) { dist.param(decltype(dist.param()){new_min, dist.max()}); }
        int get_max() const { return dist.max(); }
        int get_min() const { return dist.min(); }
        int calculate() { return dist(mt); }    // besser wäre es wohl, der calculate Funktion max/min zu übergeben;
                                                // dann könntest du nämlich direkt `std::uniform_int_distribution<int>{min, max}(mt)` zurückgeben
                                                // und setter + dist-member weglassen
    
    private:
        std::uniform_int_distribution<int> dist;
        std::mt19937 mt;
    };
    
    #endif // RANDOMNUMBER_H
    


  • Vielen Dank für die Antwort!



  • @Unterfliege sagte in srand() und rand() auslagern:

    std::mt19937

    Es ist aber schon klar, das auch die hier gezeigte C++ Variante ebenso unter vielen Problemen leidet?

    1. Auf einigen Plattformen ist das gesamte Ergebnis zufällig auf anderen immer gleich.
    2. MT kann irgendwas um die 2^20000 Sequenzen Erzeugen, diese Implementation erzeugt aber maximal 2^32.
    3. Ein bestimmter Aufruf (z.B. der erste) von calculate erzeugt evtl. nicht jede Zahl mit der gleichen Wahrscheinlichkeit, auch wenn man das vom Code her erwarten könnte.
    4. Speicher und Laufzeit ist um ein Vielfaches erhöht.


  • Dieser Beitrag wurde gelöscht!


  • @TGGC Der oben gezeigte Code ist tatsächlich kein wirkliches Musterbeispiel für die Nutzung von std::mt19937. Das std::uniform_int_distribution Objekt sollte z.B. eigentlich auch lieber jedes Mal neu konstruiert werden als immer umständlich die Grenzen neu zu setzen.
    Man sollte dazu aber auch sagen, dass eine Klasse für solch eine Funktionalität insgesamt ziemlich überflüssig ist. Den std::mt19937 nutzt man eigentlich einfach an der Stelle, an der man ihn braucht bzw. reicht auch eine einfache Funktion.

    Zu deinem Punkten:

    1. Ja, das liegt daran, dass einige (wenige) Implementationen mit std::random_device immer genau die gleiche Sequenz zurückgeben und noch nicht einmal diesen Wert jedes Mal mit einem neuen Seed füttern. Das gilt meines Wissens aber nur für den MinGW (GCC for Windows). Ansonsten ist std::random_device eigentlich die beste Methode, um einen Zufallswert zu erhalten. Für etwas besseres müsste ansonsten boost verwendet werden.
    2. Was meinst du mit "diese Implementierung"? Ich vermute, dass du auf irgendeine Implementierung der Standard-Lib anspielst - kann dir dabei aber nicht sagen, wie gut das implementiert ist. Die Periodenlänge dürfte sich aber eigentlich nicht verändern. Oder meinst du, dass der std::mt19937 schlecht gesäht wurde? Das stimmt - und das schränkt tatsächlich den Algorithmus stark ein. Dazu hat der Schwertfisch auch einmal auf folgenden Link hingewiesen: http://www.gockelhut.com/cpp-pirate/random-seed.html. Ja, hätte man vermutlich in dem Beispiel besser machen sollen, das war allerdings zugegebenermaßen bevor ich den Artikel gelesen habe.
    3. Du meinst, dass man std::uniform_int_distribution jedes Mal neu konstrukieren sollte, vermute ich mal. Ja, sollte man wohl lieber, hast du recht.
    4. Mit was vergleichst du gerade? Warum ist was höher?


  • @Unterfliege ad 2: Das, was ich andernorts auch schon gesagt habe. Den mt mit nur 32 bits seeden ist. ... doof.



  • @Swordfish Aber das dürfte eigentlich nicht die Periodenlänge des Algorithmuses ändern, oder irre ich mich da? Und ich weiß ja noch nicht einmal, ob @TGGC das meinte.
    Deinen Artikel habe ich deswegen aber ja auch schon verlinkt.



  • @Unterfliege Die Periodenlänge nicht, aber die Anzahl der möglichen Perioden. Ja, meint er.



  • @TGGC sagte in srand() und rand() auslagern:

    Speicher und Laufzeit ist um ein Vielfaches erhöht.

    Speicher ja. Laufzeit von mt19937 ist aber überraschenderweise wirklich sehr gut. Davon abgesehen hab ich an deiner Kritik nichts zu kritisieren xD



  • @Swordfish also unter einer Periode verstehe ich die Anzahl an Zufällen, die der Algorithmus generieren kann, bevor er sich wiederholt. Dabei ist es dann wohl eher so, dass die Aufrufe nach 2^32 Aufrufen schlechten/gar keinen Zufall liefern (ich habe mich mit dem MT ehrlich gesagt nie direkt auseinandergesetzt). Ich will allerdings auch nicht ausschließen, dass ich diesbezüglich falsch liege.

    @hustbaer was nutzt du denn dann eigentlich als Alternative? Separate lib oder seedest du einfach nur den mt19937 besser (wie auch in dem Artikel, den ich oben verlinkt habe, gezeigt wird)? Oder akzeptierst du einfach die Nachteile?



  • @Unterfliege sagte in srand() und rand() auslagern:

    also unter einer Periode verstehe ich die Anzahl an Zufällen, die der Algorithmus generieren kann, bevor er sich wiederholt.

    Ja.

    @Unterfliege sagte in srand() und rand() auslagern:

    Dabei ist es dann wohl eher so, dass die Aufrufe nach 2^32 Aufrufen schlechten/gar keinen Zufall liefern

    Wenn du mit 32 bit seedest hast du danach wieder die selben Sequenzen.



  • @Swordfish sagte in srand() und rand() auslagern:

    Wenn du mit 32 bit seedest hast du danach wieder die selben Sequenzen.

    Okay, dann habe ich das missverstanden. Ich ging davon aus, dass std::mt19937 die restlichen Bits von seinem state einfach mit 0 intialisiert/uninitialisiert nutzt.



  • @Swordfish sagte in srand() und rand() auslagern:

    Dabei ist es dann wohl eher so, dass die Aufrufe nach 2^32 Aufrufen schlechten/gar keinen Zufall liefern

    Wenn du mit 32 bit seedest hast du danach wieder die selben Sequenzen.

    Auch wenn man nur mit 32 bit seeded, ist die Periode dennoch deutlich länger. Du hast eben nur 2^32 initial states. Die Periodenlänge ist, Achtung, man könnte es am Namen erraten, 21993712^{19937}-1 (Quelle). Diese ultralange Periode ist neben der guten Verteilung der Zahlen ja gerade toll an MT.



  • @wob Du hast mich falsch verstanden. Wenn man mit einem wert 0 <= 2^32-1 seeded hat man eine Sequenz der länge $Periodenlänge für 0, eine Sequenz der länge $Periodenlänge für 1, eine Sequenz der länge $Periodenlänge für 2, ...



  • @Unterfliege sagte in srand() und rand() auslagern:

    @hustbaer was nutzt du denn dann eigentlich als Alternative? Separate lib oder seedest du einfach nur den mt19937 besser (wie auch in dem Artikel, den ich oben verlinkt habe, gezeigt wird)? Oder akzeptierst du einfach die Nachteile?

    Das letzte mal wo ich gute Zufallszahlen* brauchte hab ich mt19937 genommen und über CryptGenRandom geseedet. Auf POSIX Systemen kann man /dev/random oder /dev/urandom verwenden. Heutzutage würde ich aber eher std::random_device verwenden.

    *: Der mt19937 ist lange nicht so gut wie viele meinen. Wenn du nen guten, schnellen, einfachen Generator willst, schau dir die Xoroshiro Familie an. Bzw. google einfach selbst mal nach dem Thema.



  • @hustbaer sagte in srand() und rand() auslagern:

    Der mt19937 ist lange nicht so gut wie viele meinen.

    Naja, ich würde mal sagen, dass der am besten General-Purpose-Zwecke erfüllende beste Pseudo Random Number Generator ist, den es fertig im C++-Standard gibt. Daher empfehle ich diesen im Regelfall für alles mögliche. Wenn man mehr braucht: gerne, spricht nichts dagegen!

    Heutzutage würde ich aber eher std::random_device verwenden.

    Ja? Als ich das hier mal vorgeschlagen hatte für ein paar Zufallszahlen, warst du vor noch nicht mal einem Jahr noch anderer Meinung:
    https://www.c-plusplus.net/forum/topic/347207/array-wörter-zufällig-aufrufen/12



  • @wob random_device() zum seeden, nicht als RNG.


Anmelden zum Antworten