Home > Artikel > Ausgabe 9/2014 > Zufallszahlen generieren und verwenden

Zufallszahlen generieren und verwenden

Achtung: Sie sind nicht angemeldet. Wenn Sie Abonnent sind und sich anmelden, lesen Sie den kompletten Artikel, laden das PDF herunter oder probieren die Beispieldatenbank aus (sofern vorhanden).

Ein Würfelspiel in Access realisiert? Oder ein Roulette-Tisch in einem Formular? Das wären denkbare Einsatzgebiete für generierte Zufallszahlen. Aber gibt es über solche Späße hinaus sinnvolle Verwendung für Zufälle in Datenbanken? Beleuchten wir in diesem Beitrag einmal, wie Zufallszahlen erzeugt und wofür sie zum Einsatz kommen können.

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1410_Zufallszahlen.mdb.

Zufallszahlen erzeugen

Rein mathematisch gesehen ist das Berechnen von Zufallszahlen eine Wissenschaft für sich, und selbst die Definition des Ausdrucks Zufall ist Thema philosophischer Abhandlungen. Ein Prozessor kennt im Prinzip nun mal nur exakte Berechnungen und kann sich nicht einfach etwas ausdenken.

Tatsächlich finden Sie nicht einmal im Windows-API eine Funktion für Zufallszahlen. High-Level-Sprachen, wie VBA, müssen sich daher etwas einfallen lassen. Gerne werden aktuelles Datum und Uhrzeit als zufällige Werte angenommen und davon Zahlen abgeleitet.

Der Systemzeitgeber spielt also häufig eine Rolle für zufällige Werte. Lange Rede, kurzer Sinn: Access oder die Datenbank-Engine selbst enthalten keine Zufallszahlengeneratoren, wohl aber VBA mit der einzigen Methode Rnd().

Die VBA-Funktion Rnd()

Wollten Sie in der Variablen x einen Zufallswert haben, so geschähe das in der kürzesten Form mit dieser Zeile:

Dim x As Double

x = Rnd()

Rnd erzeugt einen zufälligen Double-Wert im Bereich von 0 bis 1. Die Funktion basiert, wie schon angedeutet, auf dem Systemzeitgeber von Windows. Wenn Sie es schaffen, auf zwei Rechnern zur gleichen Zeit diese Funktion aufzurufen, dann wird das Ergebnis der "Zufallszahl" dasselbe sein. Versuchen Sie es aber lieber erst gar nicht, denn hier werden sogar Nanosekunden ausgewertet.

Für ein Würfelspiel reicht dieser Wertebereich nicht aus. Um aus ihm den Bereich 1 bis 6 zu erzeugen, reicht eine simple Multiplikation aus:

Dim x As Integer

x = 1 + 5 * Rnd()

Der Mindestwert von 1 muss addiert werden, sonst kämen auch Würfel ohne Punkt heraus. Aber auch die angegebene Zeile führt zu gezinkten Würfeln, denn die 6 käme seltener zum Vorschein, als ihr gebührt. Dasselbe gilt für die 1.

Der Knackpunkt nämlich ist die Zuweisung eines Double-Werts zu einer Integer-Variablen. VBA muss hier runden: Double-Werte größer als 0.5 führen zu einer 1, die anderen zu 0. Folglich erscheinen mit der Code-Zeile die Werte 1 und 6 genau halb so oft, wie die Werte 2, 3, 4, 5. Wegen dieses Rundungsverhaltens wäre die Zeile dergestalt zu korrigieren:

Dim x As Integer

x = 0.5 + 6 * Rnd()

Die 1 erscheint nun bei Zufallswerten von etwa 0.51 bis 1.5, die 6 bei Werten von 5.51 bis 6.5.

Diesen Umstand sollten Sie immer im Auge behalten, wenn Sie die Rückgabe von Rnd in Integer-Werte konvertieren.

Eigentlich wäre nun bereits alles gesagt, würde Rnd nicht noch mit einem Parameter aufwarten, der die Steuerung der Rückgabewerte ermöglicht.

Startwerte von Rnd()

Der optionale Parameter von Rnd kann unterschiedliche Bedeutungen einnehmen, je nachdem, wie groß er ist. Ein möglicher Wert ist die Null:

Dim x As Double

x = Rnd(0)

Wenn Sie diese Zeile ausführen, erhalten Sie als Ergebnis jedes Mal den gleichen Wert. Um das zu erläutern, muss etwas weiter ausgeholt werden, wie Rnd arbeitet. Beim allerersten Aufruf der Funktion nämlich initialisiert VBA den Zufallsgenerator mit einem Startwert, der sich vom Zeitgeber ableitet. Bei allen weiteren Aufrufen aber spielt dieser keine Rolle mehr. Er wird nicht mehr abgefragt, sondern die weiteren Zufallszahlen einer Serie ausgegeben, die VBA intern anlegt beziehungsweise berechnet.

Sie können sich das wie eine Tabelle vorstellen, in der zufällige Zahlen gespeichert sind. Beim ersten Aufruf wird der Datensatzzeiger auf einen zufälligen Datensatz gesetzt und anschließend alle weiteren Datensätze ab diesem ausgegeben. Rnd(0) entspricht nun genau dem Datensatz mit dem ersten Zeiger. Ein weiterer Aufruf von Rnd ohne Parameter ändert diesen Zeiger aber wieder auf Basis des Systemzeitgebers. Rnd(0) gibt also immer den zuletzt generierten Wert zurück.

Ein anderer Parameterwert wäre eine Zahl (Single) größer als 0:

Dim x As Double

x = Rnd(0.3)

x = Rnd(99.5)

Obwohl Sie hier beliebige Single-Zahlen angeben können, spielt ihr Wert für das Ergebnis keine wesentliche Rolle. Die Zahl setzt den Zeiger auf die Zufallsserie lediglich um mehr, als eine Position, nach vorn. Eine Zufallszahl kann aber nicht zufälliger sein, als eine andere! Die Verwendung dieses Parameters kommt damit dem Aufruf der Funktion ohne Parameter gleich. Sie können Parameterwerte größer, als 0, demnach ignorieren.

Anders sieht es aus, wenn ein Parameterwert kleiner 0 gesetzt wird:

Dim x As Double

x = Rnd(-14)

x wird damit immer mit derselben Zufallszahl gefüllt. Setzen Sie statt der -14 eine -13 ein, so ändert sich die zurückgelieferte Zahl.

Der Parameter kommt damit der absoluten Positionierung des Zeigers auf die Zufallsserie gleich, während die positiven Parameterwerte relative Positionierung bedeuteten.

Sie können Zufallszahlen so immer wiederholen. Wofür ist das gut? Tja, außer einem manipulierten Casinospiel fällt uns dazu auch nicht viel ein. Sie werden dieses Verhalten sehr selten benötigen.

Randomize

Eng verbündet mit der Rnd-Funktion ist die VBA-Anweisung Randomize. VBA erzeugt ja, ausgehend von einem auf dem Zeitgeber basierenden Ausgangspunkt, die Zufallsserie immer nach dem gleichen Schema, einem Algorithmus, der keinen wirklichen Zufall mehr enthält. Auf Rechner A sähe die Serie exakt gleich aus, wie auf Rechner B, so der erste Aufruf von Rnd zum selben Zeitpunkt erfolgte. Sie können die Zufälligkeit gewissermaßen erhöhen, indem Sie Randomize aufrufen:

Dim x As Double

x = Rnd()

Randomize

x = Rnd()

Randomize zwingt den Zufallsgenerator erneut den Systemzeitgeber abzufragen, einen neuen Startwert zu setzen, und damit die Serie neu zu belegen. Auch hier stellt sich wieder die philosophische Frage nach der Zufälligkeit eines Zufalls. Streuen Sie einfach Randomize gelegentlich ein, wenn sie lange Serien von Zufallszahlen erzeugen, um Ihr Gewissen zu beruhigen.

Verteilung des Zufalls

Machen wir uns an die statistische Auswertung der von Rnd() generierten Zufallszahlen. Man könnte erwarten, dass bei einer großen Menge von Zufallszahlen alle genau gleich häufig auftreten. Um das zu überprüfen, rufen Sie die Prozedur FillTableRandom in der Beispieldatenbank, Modul mdlZufall, auf. Sie füllt die Tabelle tblZufall mit 20.000 zufälligen Werten im Bereich von 0 bis 100 (siehe Listing 1).

Sub FillTableRandom()

     Dim rs As DAO.Recordset

     Dim i As Long

     Dim n As Long

     CurrentDb.Execute "DELETE FROM tblZufall"

     Set rs = CurrentDb.OpenRecordset("tblZufall", dbOpenDynaset)

     n = 20000

     For i = 0 To n

         If (i Mod 200) = 0 Then Randomize

         rs.AddNew

' rs!Zahl.Value = 100 * Rnd 'Falsch!

         rs!Zahl.Value = 101 * Rnd – 0.5

         rs.Update

     Next i

     rs.Close

End Sub

Listing 1: Code zum Befüllen der Tabelle tblZufall mit 20000 Zufallszahlen

Das Feld Zahl der Tabelle ist als Long definiert, was zu den besprochenen Rundungsproblemen führt, weshalb die korrekte Zeile für die Wertzuweisung so lautet:

rs!Zahl.Value = 101 * Rnd – 0.5

Jeden 200ten Durchlauf der Schleife wird zusätzlich die Randomize-Anweisung aufgerufen. Im Ergebnis sieht die Tabelle dann etwa aus, wie in Bild 1. Um die statistische Verteilung der Zufallszahlen zu ermitteln wird die Abfrage aus Bild 2 auf die Tabelle angesetzt. Zu jeder Zahl von 0 bis 100 wird deren Anzahl berechnet. Diese Abfrage wiederum ist Grundlage für ein PivotChart-Formular, das die Auswertung grafisch aufbereitet. Dazu erzeugen Sie ein neues Formular frmStatistik in der Datenbank, spendieren ihm als Datenherkunft die Abfrage qry_ZufallStatistik, und stellen die Ansicht auf PivotChart.

Inhalt der Tabelle tblZufall

Bild 1: Inhalt der Tabelle tblZufall

Statistische Auswertung der Tabelle (Abfrage)

Bild 2: Statistische Auswertung der Tabelle (Abfrage)

Der Entwurf des Formulars ist, ähnlich, wie bei einem Datenblatt, sehr einfach gestaltet (Bild 3). Die Formatierung des Charts erfolgt erst, wenn Sie es in der Formularansicht öffnen. Sie können dann die Wertebereiche der Achsen bestimmen, den Typ des Diagramms ändern (hier: Balkendiagramm), oder die Farben der Elemente einstellen, indem Sie auf die jeweiligen Elemente klicken und im daraufhin sich öffnenden Dialogfenster Angaben in den entsprechenden Steuerelementen machen.

Das Pivot-Formular im Entwurf

Bild 3: Das Pivot-Formular im Entwurf

Heraus kommt eine Ansicht wie in Bild 4. Trotz der relativ hohen Anzahl an Stichproben – 200 je Wert – variiert die Verteilung doch deutlich. Nicht alle Werte sind gleichmäßig vertreten. Ohne ins Detail zu gehen, lässt sich sagen, dass dies dennoch normal ist. In der Stochastik gibt es sogar Berechnungsmethoden für die Größe der durchschnittlichen Abweichung bei einer bestimmten Ausgangsmenge von Zufallszahlen.

Verteilung von per Rnd() generierten Zufallszahlen

Bild 4: Verteilung von per Rnd() generierten Zufallszahlen

Sie können das leicht selbst überprüfen: Setzen Sie das Schleifenlimit in der Prozedur einfach herauf. Sie werden feststellen, dass sich der obere Rand der Balken immer mehr einer Line annähert. Bei einer Million Datensätzen sieht das dann schon recht gleichmäßig aus.

Interessant ist nun, ob die Anweisung Randomize hierauf einen Einfluss hat. Spielen Sie mit dem Wert des Mod-Operators, der in obigem Listing auf 200 gesetzt ist. Lassen Sie es dabei bei 20.000 Zahlen. Resultat wird sein, dass es völlig gleichgültig ist, ob oder wie oft Randomize aufgerufen wird. Da Randomize den Systemzeitgeber abfragt, könnten Sie auf die Idee kommen, dass bei der Geschwindigkeit der Schleife vielleicht einfach immer derselbe Zeitpunkt abgefragt wird?

Aber bauen Sie doch ein DoEvents oder eine andere Verzögerung in die Schleife ein, und Sie werden sehen, dass dies alles nichts am Ergebnis ändert. Damit Sie bei der Beurteilung nicht nur auf die Optik des Charts angewiesen sind, finden Sie in der Beispieldatenbank zusätzlich eine weitere Abfrage qry_StatistikZufall2, die (siehe Bild 5) auch die Standardabweichung der Zufallswerte ermittelt. Sie schwankt lediglich um das Zentrum von 14, egal, ob mit oder ohne Randomize-Anweisung.

Standardabweichung der generierten Zufalszahlen per Abfrage

Bild 5: Standardabweichung der generierten Zufalszahlen per Abfrage

Fassen wir an dieser Stelle zusammen: Je höher die Ausgangsmenge an per Rnd erzeugten Zufallszahlen, desto gleichmäßiger die Verteilung der Ergebnisse. Und auf den Einsatz von Randomize kann verzichtet werden, wenn es nicht um wiederholbare Werte geht, Sie also Rnd ohne den Parameter 0 oder kleiner verwenden.

Andere Verteilung der Zufallszahlen

Nun kann es sein, dass Sie gar keine gleichmäßige Verteilung der Zufallszahlen haben möchten. Dazu muss die Rückgabe von Rnd lediglich einigen Folgeberechnungen unterzogen werden.

Im Beispiel soll eine Gausssche Normalverteilung angestrebt sein. Das statistische Ergebnis soll ungefähr so aussehen, wie in Bild 6. Vorneweg: Wir verkünsteln uns hier nicht mit aufwändigen Berechnungen, da VBA zu wenig Funktionen bereit hält, die eine Gauss-Berechnung ohne komplizierte Subroutinen ermöglicht. Sie erreichen eine Annäherung, indem Sie etwa in der Prozedur aus Listing 1 die Zufallszahlen so berechnen:

Neue Verteilung, durch Folgeberechnungen von Rnd in Form gebracht

Bild 6: Neue Verteilung, durch Folgeberechnungen von Rnd in Form gebracht

Sie haben das Ende des frei verfügbaren Teil dieses Artikels erreicht!

Wenn Sie mehr lesen und auf viele weitere Artikel zugreifen möchten, melden Sie sich als Abonnent unter Login an. Falls nicht, bestellen Sie doch einfach ein Jahresabonnement!