Forum: Mikrocontroller und Digitale Elektronik Zufallsgenerator mit rand()


von Rahel (Gast)


Lesenswert?

Ich habe da ein kleines Problem.
Die realisierung einer Bowlingbahn auf einem Mikrokontroller (LEMPS12) 
ist ganz gut gelungen, nur möchte ich dieses Programm erweitern, so das 
die Anzeige (4 LED's) per zufall leuchtet.

Das Programm ist wie folgt:

/*********************************************************************** 
********

    Titel:      Bowlingbahnsteuerung.c

    File:       Bowlingbahnsteuerung
    Funktion:

    Hardware:
    Verfasser:  R.Mischler
    Datum:      20.03.2009
    Version:    1.0

************************************************************************ 
*******/

/* Einbinden von bestehenden Modulen (hc12.h und lbios.h nur für L12)*/
#include <stdio.h>        /* C Standard I/O-Funktionen */
#include <hc12.h>         /* Registerdefinitionen HC12 */
#include <lbios_gcc.h>    /* Interface zu LBIOS Assemblerprogrammen */
#include <limits.h>       /* Bibliothek für Zufallsgenerator */
#include <stdlib.h>       /* Bibliothek für Zafallsgenerator */

// Input
#define Uebertrit                  (PORTD & 0x01)
#define Kugelauffangsensor         (PORTD & 0x02)
#define Eingabetaste               (PORTD & 0x80)       // Taste um aus 
der Zufallsschleife zu gelangen

#define Zaehlautomatik             (PORTD & 0x3C)

// Output
#define AufstellautomatikEin       (PORTJ | 0x01)
#define AufstellautomatikAus       (PORTJ & 0xFE)
#define KugelruecklaufEin          (PORTJ | 0x02)
#define KugelruecklaufAus          (PORTJ & 0xFD)
#define FehlfunktionLedEin         (PORTJ | 0x04)
#define FehlfunktionLedAus         (PORTJ & 0xFB)

#define StrickeAnzeigeEin          (PORTJ | 0x78)
#define StrickeAnzeigeAus          (PORTJ & 0x87)
#define AnzeigeUmgeworfenePinsEin  (PORTJ | 0x78)
#define AnzeigeUmgeworfenePinsAus  (PORTJ & 0x87)

#define RAND_MAX
/*******************************************************************/
/* Unterfunktion */
void AufstellautomatikUndKugelruecklaufAktiviern(void)
{
//lokale Variablen
  int i;
  i = 0;

  PORTJ = 0x00;
  PORTJ = AufstellautomatikEin;

  for (i = 0; i < 6; i++)
  {
    PORTJ = KugelruecklaufEin;
    DelayXms(250);
    PORTJ = KugelruecklaufAus;
    DelayXms(250);
  }
  DelayXms(2000);
  PORTJ =  AufstellautomatikAus;
}

void ZufallsZahlAuswahlAktivierung(void)
{
// lokale Variablen
  int AnzahlKegel;

  AnzahlKegel = rand();
  while ( AnzahlKegel <=10 )
  {
    AnzahlKegel = rand();
  }
}

/*****************************************************************/
/* Hauptfunktion (wird beim Start ausgeführt) */
int main(void)
{
  int i, AnzahlUmgeworfeneKegel, UebertritAnfang, AnzahlWuerfe, 
AnzahlKegel;
      AnzahlWuerfe = 0;

  DDRJ = 0xFF;
  DDRD = 0x00;
  while( AnzahlWuerfe <= 10)
  {
    if (  Kugelauffangsensor != 0 )
    {
      PORTJ = 0x00;
      AnzahlWuerfe++;
      UebertritAnfang = Uebertrit;
      AnzahlUmgeworfeneKegel = Zaehlautomatik;
      AnzahlUmgeworfeneKegel = AnzahlUmgeworfeneKegel >> 2;
      AufstellautomatikUndKugelruecklaufAktiviern();
      while ( Kugelauffangsensor != 0 )
      {
        PORTJ = FehlfunktionLedEin;
        DelayXms(100);
        PORTJ = FehlfunktionLedAus;
        DelayXms(100);
      }
      if ( (UebertritAnfang == 0) & (Uebertrit == 0) )
      {
          while ( AufstellautomatikAus == 0 )
          {
            ZufallsZahlAuswahlAktivierung();
            AnzahlUmgeworfeneKegel = AnzahlKegel;
            if ( AnzahlUmgeworfeneKegel >= 10 )
            {
              for ( i = 0; i < 6; i++ )
              {
                PORTJ = StrickeAnzeigeEin;
                DelayXms(100);
                PORTJ = StrickeAnzeigeAus;
                DelayXms(100);
              }
              PORTJ = StrickeAnzeigeEin;
            }
            else
            {
              AnzahlUmgeworfeneKegel = AnzahlUmgeworfeneKegel << 3;
              PORTJ = AnzahlUmgeworfeneKegel;
            }
          }
      }
      else
      {
        PORTJ = FehlfunktionLedEin;
      }
      if ( AnzahlWuerfe == 10 )
      {
        Sound(1000);
        DelayXms(50);
        NoSound();
        DelayXms(3000);
        PORTJ = 0xFF;
        Sound(1000);
        DelayXms(100);
        NoSound();
        DelayXms(5000);
        PORTJ = 0x00;
        AnzahlWuerfe = 0;
      }
    }
  }
  return 0;
}

Nun meine Frage wär, wie ich das genau anstellen kann, das ich im 
Bereich von ZufallsZahlAuswahlAktivierung eine 'zufällige' Zahl zwischen 
0 und 10 bekomme.

Danke scho im voraus für eure bemühungen :)
Mfg

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Probiers mal mit
  AnzahlKegel = rand()%11;   /* Pseudo-Zufallszahl von 0-10*/

von Karl H. (kbuchegg)


Lesenswert?

Ist eine Möglichkeit.

Aber sehen wir uns daoch mal deine Variante an (die ist nämlich vom 
prinzipiellen Gedankengang gar nicht so schlecht, auch wenn du das nicht 
weißt) und wo das Problem dort liegt.
1
void ZufallsZahlAuswahlAktivierung(void)
2
{
3
// lokale Variablen
4
  int AnzahlKegel;
5
6
  AnzahlKegel = rand();
7
  while ( AnzahlKegel <=10 )
8
  {
9
    AnzahlKegel = rand();
10
  }
11
}

rand liefert eine Zahl zwischen 0 und RAND_MAX. D.h. AnzahlKegel kriegt 
mal irgendeine Zahl.
Und dann gehts in die while Schleife. Die wird solange ausgeführt, 
solange AnzahlKegel kleiner oder gleich 10 ist. Sagen wir mal rand() 
hätte 6 geliefert. Dann gehts in die while Schleife hinein und es wird 
eine neue Zufallszahl generiert. Die sei 8. Das Kriterium für die 
Schleife ist immer noch erfüllt (8 ist kleiner oder gleich 10) und die 
while Schleife dreht die nächste Runde -> neue Zufallszahl bestimmen. 
rand() liefert 48. Und jetzt ist die Bedingung für eine erneute 
Wiederholung der Schleife nicht mehr gegeben und die Funtion endet.

Das ist also genau das umgekehrte dessen was du willst. Die Bedingung 
für die Schleifenwiederholung ist ganz einfach verkehrt herum.

->   while( AnzahlKegel > 10 )

Aber es geht noch weiter. Was passiert den mit der so ermittelten 
Zufallszahl. Da AnzahlKegel eine lokale Variable dieser Funktion ist: 
nichts.
Du gibst diese Anzahl nicht als Returnwert der Funktion zurück, noch 
machst du sonst etwas damit.
1
int ZufallsZahlAuswahlAktivierung(void)
2
{
3
  int Zahl;
4
5
  Zahl = rand();
6
  while ( Zahl > 10 )
7
  {
8
    Zahl = rand();
9
  }
10
11
  return Zahl;
12
}

Jetzt kannst du beim Aufruf die so ermittelte Zahl aus der Funktion 
empfangen und damit weiterarbeiten:
1
....
2
            AnzahlUmgeworfeneKegel = ZufallsZahlAuswahlAktivierung();
3
....

Beachte: Die Variable AnzahlKegel in deiner Funktion hat nichts mit der 
gleichnamigen Variable in main() zu tun. Das sind 2 verschiedene 
Variablen. Wenn du die eine veränderst, veränderst du nicht automatisch 
die andere.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Allerdings kann
1
  Zahl = rand();
2
  while ( Zahl > 10 )
3
  {
4
    Zahl = rand();
5
  }
ziemlich lange dauern, weil 10 recht klein ist und MAX_INT sehr groß 
sein kann...
Durchschnittlich kommen da bei einem 16-Bit-uC also 3000 Fehlversuche 
auf einen Treffer.

von Karl H. (kbuchegg)


Lesenswert?

Lothar Miller wrote:
> Allerdings kann
>
1
>   Zahl = rand();
2
>   while ( Zahl > 10 )
3
>   {
4
>     Zahl = rand();
5
>   }
6
>
> ziemlich lange dauern, weil 10 recht klein ist und MAX_INT sehr groß
> sein kann...


Ah, ja.
Dazu wollt ich noch was schreiben, ehe ich gemerkt habe, dass seine 
Funktion keinen Returnwert hat.

Was ist das Problem?
Das Problem ist in der Mathematik als das "Schubladenproblem" bekannt. 
Es ist unmöglich m Gegenstände in n Schubladen zu legen, sodass in allen 
Schubladen gleich viele Gegenstände liegen, wenn m kein ganzzahliges 
Vielfaches von n ist.

Oder etwas weniger mathematisch angehaucht gesagt: Ich kann machen was 
ich will, wenn ich 8 Paar Socken in 5 Schubladen gleichmässig verteilen 
soll, werde ich Schiffbruch erleiden. Egal was ich tue. 8 ist kein 
Vielfaches von 5. Erst mit 10 Paar Socken geht das wieder und dann sind 
in jeder Schublade 2 Paar Socken.

In der Umkehrung heist das aber auch, wenn ich 32767 verschiedene 
mögliche Zufallszahlen habe und ich möchte daraus Zufallszahlen von 0 
bis 10 (also 11 Stück) generieren, dann kann ich tun was ich will, ich 
werde es nie schaffen, dass alle Zahlen gleich häufig auftreten. 11 ist 
nun mal kein ganzzahliger Teiler von 32767

Mal mit etwas kleineren Zahlen veranschaulicht: sei die größte von 
rand() generierbare Zufallszahl gleich 5 und ich möchte Zahlen von 0 bis 
3 haben, dann wird durch eine Modulo-Division folgende Abbildung erzielt

     0   0   (weil 0 % 4 gleich 0)
     1   1   (weil 1 % 4 gleich 1)
     2   2
     3   3
     4   0   (weil 4 % 4 gleich 0)
     5   1

generiert also rand() alle Zahlen von 0 bis 6 gleichmässig, dann 
entstehen durch die Modulo Division weniger 3-en und 2-en als 0-en und 
1-en. Und zwar ganz schön heftig.
Seien von 100 Zufallszahlen 20 Stück 0, 20 Stück 1, 20 Stück 2, etc 
(also perfekt gleichverteilt), so münden diese 100 Stück Zufallszahlen 
darin, dass daraus 40 Stück 0-en, 40 Stück 1-en, 20 Stück 2-en und 20 
Stück 3-en entstehen. Von einer Gleichverteilung kann also keine Rede 
mehr sein: 0 und 1 sind doppelt so häufig wie 2 und 3

Klar bei einem RAND_MAX von 32767 ist dieser Effekt nicht mehr sehr 
ausgeprägt, weil der prozentuale Fehler kleiner wird, wenn RAND_MAX sehr 
groß ist im Vergleich zur größten gewünschten Zufallszahl. Aber er ist 
vorhanden. Ob man das in einem Bastlerprojekt gewillt ist zu tolerieren, 
muss man im Einzelfall entscheiden.

Aber was ist ein Ausweg aus dem Dilemma?
Zurück zu den Socken: Da es keine Möglichkeit gibt, bleibt nur eines. 
Einige Socken werden verworfen. Wenn 17 Socken auf 5 Laden aufzuteilen 
sind, werden 2 Socken zur Seite gelegt und die restlichen 15 Socken auf 
die 5 Laden gleichmässig verteilt. Eine andere Möglichkeit gibt es 
nicht. Und dieses 'gibt es nicht' ist unabhängig davon, was ich mit den 
von rand() generierten Zahlen anstelle. Manchmal sieht man im Web die 
Empfehlung sich doch zunächst mal aus den rand()-Werten eine double Wert 
im Bereich 0 bis 1 zu machen, etc. Aber diese Leute unterschätzen die 
Mathematik. Wenn Mathe einmal sagt: Es ist unmöglich, dann ist es das 
auch wirklich!
Warum gerade 2? Nun weil 15 die nächst kleinere Zahl an Socken ist, die 
ein Vielfaches von 5 Laden darstellt.

Das kann man auch auf Zufallszahlen übertragen.
Man bestimmt sich zunächst die Grenze, bis zu der man gewillt ist, 
Zahlen die rand() liefert zu akzeptieren. Mathematisch gesprochen: 
Gesucht ist wieder mal das größte Vielfache von in diesem Fall 11, 
welches gerade noch kleiner als RAND_MAX ist.
Alle Zahlen aus rand() die größer als diese Zahl ist, werden verworfen 
und nicht zur Generierung der eigentlich gewünschten Zufallszahl 
herangezogen. Mit dem Rest der Zahlen kann man dann die Umrechnung 
machen.

1
int myRand()
2
{
3
  int x;
4
5
  while( (x = rand()) >= RAND_MAX - (RAND_MAX % UpperBound) )
6
    ; 
7
  return x % UpperBound;
8
}

von Hagen R. (hagen)


Lesenswert?

@Karl heinz:

Naja, aber was wäre wenn man den kleinsten gemeinsammen Teiler benutzen 
würde.

Also unser Rand() kann die Entscheidung auf binärer Ebene zwischen 0 und 
1 doch ziemlich gleichverteilt treffen, richtig ?
Nun bauen wir uns ein Array[] mit 11 Elementen und schreiben dahinein 
die Zahlen 1 bis 11.
Wenn wir eine Zahl erzeugen wollen gehen wir so vor:
1.) gehe das Array[] mehrmals von Unten nach Oben durch
2.) in jedem Schritt erzeuge mit Rand() eine Zahl im Set {0,1}
3.) ist es die 1 dann vertausche die beiden Zahlen am aktuellen Index 
und (Index +1) % Length(Array[])

Nachdem man das mehrmals gemacht hat liefere die Zahl am Array Index 0 
als Ergebnis zurück. So müsste man den Fehler auf das Minimalste 
reduzieren können auf 1/Periode des RNGs wenn die Periode ungerade ist 
und auf 0 wenn die Periode gerade ist.

Gruß Hagen

von Rahel M. (rahel1991)


Lesenswert?

Danke euch allen für eure Mühe ;)
Nur hab ich jez noch ein Problem...

Wenn ich diese Idee benutze:(und alles andere so lase wie es anscheinden 
sein muss :D)

int myRand()
{
  int x;

  while( (x = rand()) >= RAND_MAX - (RAND_MAX % UpperBound) )
    ;
  return x % UpperBound;
}

Dann steht beim 'Compiler'-ähnlichem Programm --> undefined reference to 
rand
Und das bei jeder Zeile, auf der dieses rand verwendet wird.

Auch wenn ich die andere Idee nehme :
int ZufallsZahlAuswahlAktivierung(void)
{
  int Zahl;

  Zahl = rand();
  while ( Zahl > 10 )
  {
    Zahl = rand();
  }

  return Zahl;
}
dann noch
AnzahlUmgeworfeneKegel = ZufallsZahlAuswahlAktivierung();
einfüge.
Kommt genau die gleiche Meldung. Könnte das sein, dass das Programm 
diesen Befehl gar nicht kennt, obwohl ich die richtige Library eingefügt 
habe? Oder liegt das an was anderem?

Noch zur letzten Idee, die würde sicherlich auch Funktionieren (wenn 
nicht das gleiche Auftrtitt wie oben...) nur leider bin ich nicht gerade 
ne 'Heldin' was Arrays angeht... aber Danke trotzdem ;)

Und nochmal ein grosses Dankeschön an alle die sich die Mühe gemacht 
haben und mir evt. auch beim nächsten helfen können :) :P

Mfg Rahel und schon bald ein gutes Wochenende :)

von Karl H. (kbuchegg)


Lesenswert?

Rahel Mischler wrote:

> Dann steht beim 'Compiler'-ähnlichem Programm --> undefined reference to
> rand
> Und das bei jeder Zeile, auf der dieses rand verwendet wird.

'undefined reference' bedeutet immer, dass der Compiler auf ein Wort 
(Funktionsname oder Variablenname) gestossen ist, das er nicht kennt. In 
der Sprache C gibt es nur relativ wenige Schlüsselwörter, die der 
Compiler von Haus aus kennt. int, for, if, while etc. gehören dazu. 
Alles andere wird durch vordefinierte Funktionen abgedeckt, wie zb 
sin(), cos(), sqrt() oder eben auch rand().

Um eine Funktion verwenden zu können, muss der Compiler vor der 
Verwendung wissen, dass es diese Funktion überhaupt gibt. Dazu muss es 
eine Funktionsdeklaration geben. Der Compiler möchte nämlich ganz gerne 
überprüfen, ob es eine Funktion gibt und wenn ja, welche Datentypen die 
übergebenen Argumente haben müssen bzw. was der Returnwert dieser 
Funktion für einen Datentyp hat. Und da er die Funktionen von Haus aus 
nicht kennt, muss man das vor der ersten Verwendung klarstellen.

Für die vorgefertigten, mitgelieferten Funktionen befinden sich diese 
Funktionsdeklarationen allesamt in sog. Header Files, die mit #include 
üblicherweise am Anfang deines Programmtextes eingebunden werden. Um 
eine bestimmte Funktion zu verwenden, muss man also wissen in welchem 
Header File (es gibt davon mehrere, nach Themenkreisen geordnet) sich 
die Deklaration der Funktion befindet. Diese Information wiederrum 
bekommt man aus seinem C-Lehrbuch, bzw. in dem man das Hilfesystem 
seines C-Systems nach der Funktion befragt, dort steht das normalerweise 
auch immer mit dabei.

In deinem Fall ist es so, dass die entsprechende Funktionsdeklaration in 
stdlib.h enthalten ist (stdlib : STanDardLIBrary)

Also am Anfang einfach
#include <stdlib.h>

und dann sollte rand() bekannt sein.

Und demnächst an in die nächste Buchhandlung, und ein Standard-Werk über 
C kaufen. Du wirst es brauchen.

von Karl H. (kbuchegg)


Lesenswert?

Hagen Re wrote:

> Also unser Rand() kann die Entscheidung auf binärer Ebene zwischen 0 und
> 1 doch ziemlich gleichverteilt treffen, richtig ?
> Nun bauen wir uns ein Array[] mit 11 Elementen und schreiben dahinein
> die Zahlen 1 bis 11.
> Wenn wir eine Zahl erzeugen wollen gehen wir so vor:
> 1.) gehe das Array[] mehrmals von Unten nach Oben durch
> 2.) in jedem Schritt erzeuge mit Rand() eine Zahl im Set {0,1}
> 3.) ist es die 1 dann vertausche die beiden Zahlen am aktuellen Index
> und (Index +1) % Length(Array[])
>
> Nachdem man das mehrmals gemacht hat liefere die Zahl am Array Index 0
> als Ergebnis zurück. So müsste man den Fehler auf das Minimalste
> reduzieren können auf 1/Periode des RNGs wenn die Periode ungerade ist
> und auf 0 wenn die Periode gerade ist.

Ich denke, das könnte funktionieren.
Allerdings bin ich sehr vorsichtig geworden, was Gleichverteilungen 
angeht. Ich hab eines gelernt: Es ist unheimlich einfach, unbemerkt eine 
Gleichverteilung zu zerstören.
Da so ein Verfahren aber verwendet wird um eine zufällige Permutation 
von n Zahlen (n war in dem Beispiel 11) zu erzeugen, denke ich, es ist 
ok. Müsste man mal ausprobieren und ein paar Zufallszahlentests drüber 
laufen lassen.

Und ehe jemand nachfrägt: Nein, mann muss wirklich bei jedem Aufruf eine 
neue Permutation erzeugen. Es würde nicht reichen, einmal eine 
Permutation für jeweils 11 Aufrufe zu erzeugen. Hat man aus dieser 
Permutation 10 Werte abgeholt, kann jeder mit einem IQ größer als 15 die 
als nächstes zu liefernde Zahl, die 11.te, vorhersagen und das wär dann 
nicht mehr zufällig)

von Rahel M. (rahel1991)


Lesenswert?

Danke für die Idee mit dem Buch, aber hab schon eins ;) und das ich das 
brauchen werde is auch klar, bin ja erst im zweiten Lehrjahr...

Das mit der Library is mir klar, aber was komisch is, dass diese Meldung 
kommt, obwohl ich diese Bibliothek eingebunden habe.
Alls ich angefangen habe, dieses Programm umzusetzten, musste ich erst 
einmal schauen wo das welche Befehle/Anwendungen drin stehen, und da 
fand ich die Bibliotheken, welche auch oben 'eingebunden' wurde.
Kann das sein, das dieser Befehl einfach nicht erkannt wird? (Obwohl die 
restlichen der gleichen Bibliothek erkannt werden...)
Hab leider auch kein Buch zum Board selber, da ichs in der Berufschule 
kaufen musste... und ne brauchbare hilfe is im Programm selber auch 
nicht aufzufinden.

von Karl H. (kbuchegg)


Lesenswert?

Zunächst mal:
Du 'bindest' keine Bibliothek ein. Ein Header File ist ein Textfile wie 
jedes andere auch. Das kann man im Editor aufmachen und reinschauen, was 
da drinnen steht (wenn man die Datei erst mal gefunden hat)

Ist bei dir in der stdlib.h  eine Funktion rand() angegeben?


Nach einem Kaffee ist mir auch der Gedanke durch den Kopf geschossen, 
dass 'undefined reference' eigentlich eher eine Linker-Fehlermeldung 
ist. Der Compiler würde da eher 'undeclared identifier' oder sowas in 
der Richtung von sich geben.
Das heist: Dir fehlt beim Linken tatsächlich eine Bibliothek. Das wäre 
allerdings in der Tat ungewöhnlich, denn die C-Standard Funktionen sind 
allesamt in der C-Runtime Library gesammelt, und diese wird automatisch 
immer mit eingelinkt.

Wie war denn das bei deinem ursprünglichen Program?
Dort hast du ja auch rand() benutzt. Hat das geklappt?

von Rahel M. (rahel1991)


Lesenswert?

Ja, die Funktion ist in der Header-Datei vorhanden.
Und nei, das hat schon dort nicht geklappt, und dachte mir es könnte an 
was anderem liegen, darum hab ich überhaupt erst mit dem Eintrag 
angefange.

Hättest du mir sonst ne Idee, wie ich so nen 'Zufallsgenrator' machen 
kann ohne das rand(). Ausser du wüsstes wie man diesen Fehler beheben 
kann? ;)
Mfg

von Simon K. (simon) Benutzerseite


Lesenswert?

Rahel Mischler wrote:
> Ja, die Funktion ist in der Header-Datei vorhanden.
Hehe, nein. Die Funktion ist in der Header-Datei nicht vorhanden. Nur 
deren Prototyp. Sourcecode findest du da nämlich nicht.

> Hättest du mir sonst ne Idee, wie ich so nen 'Zufallsgenrator' machen
> kann ohne das rand(). Ausser du wüsstes wie man diesen Fehler beheben
> kann? ;)
Schau mal hier:
Beitrag "Re: Zufallsgenerator - ungleiche Startwerte erzeugen"

von Rahel M. (rahel1991)


Lesenswert?

Dann halt angegeben :) :P
Danke für den Link. ;) aber hättest mir auch ne leichtere Idee, die ne 
2.Lehrjahr elektronikerin versteht ;)
Mfg

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.