Würfeln



  • @Marc++us:
    Bildet diese Klasse Deine Idee richtig ab?

    class Wuerfel 
        { 
              const unsigned int maxzahl_;
              const unsigned int maxrandom_;
            public: 
              Wuerfel(unsigned int maxzahl):maxzahl_(maxzahl),maxrandom_(RAND_MAX-(RAND_MAX%maxzahl)){ srand( (unsigned)time( NULL ) ); } 
              unsigned int wuerfelt()
              {  
                unsigned int r;
                do{ r = rand(); } 
                    while ( r >= maxrandom_ );
                return ( r % maxzahl_ +1 ); 
              } 
        };
    


  • Marc++us schrieb:

    ...
    Ansonsten bekommst Du wohl zwangsläufig Ungerechtigkeiten.

    korrekt. ich kenne keinen weg, außer dem von dir genannten, um die ungerechtigkeiten wegzumachen.



  • Bei der Würfel-Funktion findet man manchmal so etwas:

    dRand = rand();
    dRand /= RAND_MAX;
    dRand = dRand * 6 + 1;
    nWuerfel = (int) dRand;
    

    Da gibt es inhaltlich doch keinen Unterschied zur Modulo-Variante?



  • Was ist im Konstruktor richtig? Manche sagen, man soll rand() direkt hinter srand() aufrufen:

    Wuerfel(unsigned int maxzahl):maxzahl_(maxzahl),maxrandom_(RAND_MAX-(RAND_MAX%maxzahl)){ srand( (unsigned)time( NULL ) ); rand(); }
    

    Macht das Sinn?



  • @eh:

    Zur Klasse: grundsätzlich tut sie das, was ich meinte, aber NUR solange Du nur einen einzigen Würfel hast. Bei dieser Konstellation bereits

    Wuerfel w1, w2;
    

    kannst Du Dein blaues Wunder erleben. Das srand hat im Ctor nichts verloren.

    Entweder kapselst Du das wie von Volkard vorgeschlagen ODER Du machst mindestens sowas:

    class Wuerfel
    {
    private:
       static int m_seed;
    ... // wie zuvor
    };
    
    int Wuerfel::m_seed = srand((unsigned)time(0));
    

    Damit stellst Du immerhin sicher, daß der Seed für alle Würfel nur ein einziges Mal gesetzt wird. Somit wird dann der Fall "n Würfel" abgedeckt. Aber wenn noch jemand den Seed verändert, wird es doch lustig. Applikationstechnisch dürfte eine Klasse für den Zufallsgenerator angemessen sein.



  • Erhard Henkes schrieb:

    Bei der Würfel-Funktion findet man manchmal so etwas:

    dRand = rand();
    dRand /= RAND_MAX;
    dRand = dRand * 6 + 1;
    nWuerfel = (int) dRand;
    

    Da gibt es inhaltlich doch keinen Unterschied zur Modulo-Variante?

    schau das schiefe histogramm bei 10000 werten mal mit dieser und mit der anderen version an. dann sollte klar sein, warum manche leute sowas machen.



  • Erhard Henkes schrieb:

    Was ist im Konstruktor richtig? Manche sagen, man soll rand() direkt hinter srand() aufrufen:

    Wuerfel(unsigned int maxzahl):maxzahl_(maxzahl),maxrandom_(RAND_MAX-(RAND_MAX%maxzahl)){ srand( (unsigned)time( NULL ) ); rand(); }
    

    Macht das Sinn?

    ohne rand() danach haste oft den lustigen effekt, daß dieser code:

    for(;;)
    {
       Wuerfel w(1000);
       cout<<w.wuerfle()<<endl;
       Sleep(1*SEKUNDE);
    }
    

    ne simple jeweils um 1 aufsteigende zahlenfolge ausgibt.



  • also im spieleladen gips unter anderem würfel mit den zahlen von 1 bis 4, bis 6, bis 8, bis 12 und bis 20. man erkennt platonische körper, womit die fairness des würfels offensichtlich ist.
    aber wie sieht ein offensichtlich fairer würfel aus, der die zahlen von 1 bis 7 fair hat?



  • Bei einer Funktion Zufallszahl macht man das mit einem Flag. Man könnte in einer Klasse Wuerfel noch ein static seed_flag aufnehmen und damit srand(...) nur einmal beim Konstruktor des ersten Würfels ausführen lassen (analog zur obigen Zufalls-Funktion). IMHO gehört dies nämlich in den Konstruktor. Wäre dies eine korrekte Lösung?



  • Erhard Henkes schrieb:

    Man könnte in einer Klasse Wuerfel noch ein static srand_flag aufnehmen und damit srand(...) nur einmal beim Konstruktor des ersten Würfels ausführen lassen (analog zur obigen Zufalls-Funktion). IMHO gehört dies nämlich in den Konstruktor. Wäre dies eine korrekte Lösung?

    korrekt? korrekt mag sie sein, aber sie ist scheiße.
    nimm den code von rand() und hau ihn in deine klasse rein. und mach den seed zum attribut.



  • Oha, ich sehe gerade, daß srand den Seed gar nicht zurück gibt... das Ding ist ja void. Dann geht mein Vorschlag sowieso nicht, aber ist ohnehin nur 2. Wahl.



  • Also ich poste noch mal die Klasse mit dem seed_flag, damit das kein Singleton werden muss:

    #include <iomanip> 
    #include <conio.h> 
    #include <cstdlib>
    #include <ctime> 
    
    class Wuerfel 
    { 
      const unsigned int maxzahl_;
      const unsigned int maxrandom_;
      static bool seed_flag;
    public: 
      Wuerfel(unsigned int maxzahl):maxzahl_(maxzahl),maxrandom_(RAND_MAX-(RAND_MAX%maxzahl))
      { 
        if(!seed_flag)
        { 
          srand( (unsigned)time( NULL ) ); seed_flag = true; 
          /*std::cout << "seed-flag gesetzt." << std::endl;*/
        } 
      } 
    
      unsigned int wuerfelt()
      {  
        unsigned int r;
        do{ r = rand(); } 
          while ( r >= maxrandom_ );
        return ( r % maxzahl_ +1 ); 
      } 
    }; 
    
    bool Wuerfel::seed_flag = false;
    
    int main() 
    { 
      const unsigned long long Serie     = 3; 
      const unsigned long long Versuche  = 30000000; 
      const unsigned int limit           = 200; 
      const unsigned int moeglichkeiten  = 6; 
    
      Wuerfel w(moeglichkeiten); 
      unsigned long long H[moeglichkeiten+1]; 
    
      for(unsigned long long i=1; i<Serie+1; ++i) 
      { 
        for(unsigned int j=0; j<moeglichkeiten+1; ++j) H[j] = 0; 
    
        for(unsigned long long k=1; k<Versuche+1; ++k) 
        { 
          unsigned int wurf = w.wuerfelt(); 
          if(Versuche<limit) std::cout << wurf << " "; 
          ++H[wurf]; 
        } 
    
        for(unsigned int c=1; c<moeglichkeiten+1; ++c) 
        { 
          std::cout << std::endl << c << ": " << H[c] << " " << std::setprecision(7)  
                    << 100 * static_cast<float>(H[c]) / Versuche << " %"; 
          H[0] += H[c]; 
        } 
        std::cout << std::endl << "Wuerfe insgesamt: " << H[0] << std::endl << std::endl; 
      } 
      getch(); 
    }
    

    Jetzt könnt ihr das Ding wieder konkret zerreißen. 🙂
    Die Differenzen bei der Gleichverteilung liegen jetzt im Promillebereich. Damit sollte man leben können. Wie hoch sind die Abweichungen eigentlich bei mechanischen Würfeln, auch ca. 0,1% ?

    Geschichte des Würfels: http://www.uni-magdeburg.de/MWJ/MWJ2002/ineichen.pdf



  • Erhard Henkes schrieb:

    Wie hoch sind die Abweichungen eigentlich bei mechanischen Würfeln, auch ca. 0,1% ?

    Theoretisch 0%.
    <müll>
    Nun muss man aber bei einem Würfel bedenken, dass die Löcher Gewicht umverteilen und Einfluss auf die Aerodynamik haben. Desweiteren ist das Material sicher nicht ganz rein wodurch eine Ungerechtigkeit in der Gewichtsverteilung unvermeidbar ist. Zusätzlich nutzt sich der Würfel nach jedem Wurf ab (ob das gleichmäßig geschieht kann zum Streitpunkt werden) 😃 .
    </müll> 🤡



  • Zerreissen? Bitteschön... wie bestellt: 😉

    1. wieso ist die Methode "wuerfelt" nicht const?

    2. wieso ist das statische Seed-Flag global in der Klasse und nicht lokal im Ctor? Außer dem Ctor braucht das doch sonst keiner.



  • Erhard Henkes schrieb:

    Die Differenzen bei der Gleichverteilung liegen jetzt im Promillebereich. Damit sollte man leben können. Wie hoch sind die Abweichungen eigentlich bei mechanischen Würfeln, auch ca. 0,1% ?

    verdammt! mach mal "const unsigned int moeglichkeiten = RAND_MAX*0.6;"
    deine abweichungen wären ok, wenn sie alle zahlen mal treffen würden, aber abweichungen, die wiederholbar immer die 0 und die 1 bevorzugen, sind schlicht fehler.



  • Und die const-Elementvariablen machen das Zuweisen von Würfeln unmöglich.



  • @Marc++us: Danke für die Ergänzungen, hier die aktuelle Variante:

    #include <iostream> 
    #include <iomanip>  
    #include <conio.h>  
    #include <cstdlib> 
    #include <ctime>  
    
    class Wuerfel  
    {  
    private:
      const unsigned int maxzahl_; 
      const unsigned int maxrandom_; 
    
    public:  
      Wuerfel(unsigned int maxzahl):maxzahl_(maxzahl),maxrandom_(RAND_MAX-(RAND_MAX%maxzahl))
      { 
        static bool seed_flag = 0; 
        if(!seed_flag)
        { 
          srand( (unsigned)time( NULL ) );  seed_flag = true; 
          // std::cout << "seed-flag gesetzt." << std::endl;
        } 
      }  
    
      unsigned int wuerfelt() const
      {   
        unsigned int r; 
        do{ r = rand(); }  
          while ( r >= maxrandom_ ); 
        return ( r % maxzahl_ +1 );  
      }  
    };  
    
    int main()  
    {  
      const unsigned long long Serie     = 3;  
      const unsigned long long Versuche  = 30000000;  
      const unsigned int limit           = 200;  
      const unsigned int moeglichkeiten  = 6;  
    
      Wuerfel w(moeglichkeiten), w_binaer(2);  
      unsigned long long H[moeglichkeiten+1];  
    
      for(unsigned long long i=1; i<Serie+1; ++i)  
      {  
        for(unsigned int j=0; j<moeglichkeiten+1; ++j) H[j] = 0;  
    
        for(unsigned long long k=1; k<Versuche+1; ++k)  
        {  
          unsigned int wurf = w.wuerfelt();  
          if(Versuche<limit) std::cout << wurf << " ";  
          ++H[wurf];  
        }  
    
        for(unsigned int c=1; c<moeglichkeiten+1; ++c)  
        {  
          std::cout << std::endl << c << ": " << H[c] << " " << std::setprecision(7)   
                    << 100 * static_cast<float>(H[c]) / Versuche << " %";  
          H[0] += H[c];  
        }  
        std::cout << std::endl << "Wuerfe insgesamt: " << H[0] << std::endl << std::endl;  
      }  
      getch();  
    }
    


  • Hättest Du übrigens den Zufallszahlengenerator ausgelagert in eine Klasse, so könntest Du Deinen Würfel mit folgender wunderschöner Testklasse erproben:

    class AllNumbersAreEqual
    {
    private:
       int m_seed;
    public:
       AllNumbersAreEqual() : m_seed(RAND_MAX - 1);
       int getNum()
       {
          m_seed++;
          if (m_seed >= RAND_MAX)
             m_seed = 0;
          return m_seed;
       }
    };
    

    Dieser Generator erzeugt Dir eine aufsteigende Zahlenfolge von 0...RAND_MAX-1.

    Wenn Du diesen Generator in Deine class Wuerfel einbindest und dann einige (N * 6 * RAND_MAX) Male würfelst, MUSS bei der Häufigkeit überall exakt die gleiche Zahl rauskommen.

    Wie gesagt, das solltest Du auch mal für einen 60er Würfel testen.



  • @Marc++us:
    Danke für Deine konstruktiven Beiträge, das hilft mir sehr. Ich wollte darstellen, dass der Würfel als einheitliches Objekt selbst die Zahlen generiert. Deinen hier geposteten Testgenerator bauen wir aber sofort mittels eigener Methode "wuerfelt_mit_externem_Generator()" in den Würfel ein, damit kann man nun wählen, ob man mit rand() oder zu Testzwecken mit dem "Gleichverteiler" arbeiten will. Klasse Idee! Weiter so.

    #include <iostream> 
    #include <iomanip>  
    #include <conio.h>  
    #include <cstdlib> 
    #include <ctime>  
    
    class AllNumbersAreEqual 
    { 
    private: 
       int m_seed; 
    public: 
       AllNumbersAreEqual() : m_seed(RAND_MAX - 1){}; 
       int getNum() 
       { 
          m_seed++; 
          if (m_seed >= RAND_MAX) 
             m_seed = 0; 
          return m_seed; 
       } 
    };
    
    class Wuerfel  
    {  
    private:
      const unsigned int maxzahl_; 
      const unsigned int maxrandom_; 
      AllNumbersAreEqual zahlengenerator_;
    
    public:  
      Wuerfel(unsigned int maxzahl):maxzahl_(maxzahl),maxrandom_(RAND_MAX-(RAND_MAX%maxzahl))
      { 
        static bool seed_flag = 0; 
        if(!seed_flag)
        { 
          srand( static_cast<unsigned>(time(0)) );  seed_flag = true; 
          // std::cout << "seed-flag gesetzt." << std::endl;
        } 
      }  
    
      unsigned int wuerfelt() const
      {   
        unsigned int r; 
        do{ r = rand(); }  
          while ( r >= maxrandom_ ); 
        return ( r % maxzahl_ +1 );  
      }
    
      unsigned int wuerfelt_mit_externem_Generator() 
      { 
        unsigned int r; 
        do{ r = zahlengenerator_.getNum(); }  
          while ( r >= maxrandom_ ); 
        return ( r % maxzahl_ +1 );  
      }      
    };  
    
    int main()  
    {  
      const unsigned long long Serie     = 1;  
      const unsigned long long Versuche  = 60000000;  
      const unsigned int limit           = 200;  
      const unsigned int moeglichkeiten  = 60;  
    
      Wuerfel w(moeglichkeiten), w_binaer(2);  
      unsigned long long H[moeglichkeiten+1];  
    
      for(unsigned long long i=1; i<Serie+1; ++i)  
      {  
        for(unsigned int j=0; j<moeglichkeiten+1; ++j) H[j] = 0;  
    
        for(unsigned long long k=1; k<Versuche+1; ++k)  
        {  
          unsigned int wurf = w.wuerfelt_mit_externem_Generator(); 
          // unsigned int wurf = w.wuerfelt();
          // unsigned int wurf = w_binaer.wuerfelt();       
    
          if(Versuche<limit) std::cout << wurf << " ";  
          ++H[wurf];  
        }  
    
        for(unsigned int c=1; c<moeglichkeiten+1; ++c)  
        {  
          std::cout << std::endl << c << ": " << H[c] << " " << std::setprecision(7)   
                    << 100 * static_cast<float>(H[c]) / Versuche << " %";  
          H[0] += H[c];  
        }  
        std::cout << std::endl << "Wuerfe insgesamt: " << H[0] << std::endl << std::endl;  
      }  
      getch();  
    }
    

    @Volkard:
    Ich habe das mit 0.6*RAND_MAX und auch mit anderen Einstellungen getestet und keine Bevorzugung von 0 und 1 (weder die ersten beiden Zahlen des Würfels noch die niedrigste und höchste Zahl der erzeugten Zahlenreihe) gesehen. Daher meine Frage: Wie zuverlässig ist rand() wirklich. Eine exakte Gleichverteilung habe ich damit bisher nicht geschafft, eine Häufung bei gewissen Zahlen aber auch nicht, dafür haben wir doch maxrandom_ eingebaut. Vielleicht könntest Du mir noch einen konkreten Tipp geben.



  • Argl.

    Dein Klassenentwurf mit der Fallunterscheidung der Generatoren ist furchtbar.

    Du darfst Dir einen Entwurf aussuchen:

    1. Klassisch mit Interface

    Du erzeugst eine abstrakte Interfaceklasse "IRandomGenerator" (da will Volkard schon seit 6 Stunden hin) mit einer rein virtuellen "getNumber"-Methode.

    Davon leitest Du zwei Klassen AllNumbersAreEqual und RandFromStdlib ab (die letztere implementierst Du mit Hilfe von srand und rand).

    Unter Umständen ist es hilfreich, die Konstant RAND_MAX innerhalb der Klasse IRandomGenerator noch einmal zu "spiegeln", also dort unterzubringen. Du solltest Dich nicht mehr direkt auf RAND_MAX aus stdlib abstützen.

    Die Klasse Wuerfel bekommt einen Zeiger auf eine Instanz IRandomGenerator verpasst, dem Du jeweils eine mit new erzeugte Instanz übergeben kannst:

    void Wuerfel::changeGenerator(IRandomGenerator* pRG)
    {
       assert(pRG);
       delete m_RandomGenerator = pRG;
    }
    

    Du kannst nun mit w1.changeGenerator(new AllNumbersAreEqual) einen anderen Generator setzen.

    Achtung, die obige Implementation hat den Nachteil, daß man eine gestacktes Objekt übergeben kann. Eine alternative Implementation, die das vermeidet, überlesen wir dem geneigten Leser als Übungsaufgabe.

    Der Ctor initialisiert m_RandomGenerator mit einem Objekt der Stdlib-Klasse.

    1. Random-Policy 😉 mit Hilfe von Templates

    Du erstellst zwei Klassen AllNumbersAreEqual ohne gemeinsame Oberklasse, getNumber ist nicht virtual. Ansonsten gleiche Signatur der public-Methoden.

    Wuerfel wird eine Template-Klasse und bekommt einen Template-Parameter <typename Generator> verpasst. Die Ziehung ruft nun Generator.getNumber auf.

    Pro/Cons:
    1+ zur Laufzeit umschaltbarer Generator
    1- etwas inkonsequent, daß man den Testfall explizit umschalten muß, eigentlich könnte man ja gleich eine Testklasse von Würfel nehmen und die Exe als Testfall-Exe compilieren
    2+ nicht verwendete Generatoren bleiben außen vor, etwas schlanker
    2- nicht zur Laufzeit umschaltbar


Anmelden zum Antworten