Würfeln



  • Erhard Henkes schrieb:

    10.000.000 Würfe mit einem 6er-Würfel ergeben mit obigem Programm eine Verteilung von 16,64 bis 16,69 %. Das zeigt doch, dass rand() "gerecht" funktioniert.

    siehe oben
    5462 mal die 0 und 1.
    5461 mal die 2,3,4 und 5.
    hier steckt ein systematischer fehler drin.
    stell dir rand() vor, was 2^15 werte gibt und die folge aus rand()%10000. nu isses aber so schief, daß man's auch wegschmeißen kann. und du hast ja nicht festgelegt, daß dein prog nur mit winzigen zahlebn arbeiten darf.



  • So ein Dreck, jetzt habe ich meinen Beitrag aus Versehen gelöscht ohne ihn zu senden... ****

    Also, noch mal. Annahme: rand() sei fair.

    Das Problem mit der Schieflage (von Volkard angesprochen) läßt sich doch trivial beheben.

    Sei 32767 die größte Zahl, die der Generator liefert. Dann kann ich die Sache so fair machen:

    (Hinweis: 32766 ist die größte Zahl, die ein ganzzahliges Vielfaches von 6 ist und kleiner als 32767)

    int z;
    int r;
    
    do
    {
       r = rand();
    } while (r >= 32766);
    
    z = r%6 + 1;
    

    z enthält nun die faire Zufallszahl mit Gleichverteilung aller 6 Ziffern.

    Den Zahlenstrom habe ich so aufgeteilt:
    1,2,3,4,5,6,1,2,3,4,5,6,...,1,2,3,4,5,6,ungültig,ungültig

    Damit ist das Problem doch hinreichend gelöst.

    Apropos, theoretisch besteht die Chance, daß die Schleife nicht terminiert. Ist aber extrem unwahrscheinlich. 😉



  • @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.


Anmelden zum Antworten