| E-Mail: |
|---|
Bei Fragen zu diesem Beitrag bitte den Autor des Beitrags kontaktieren!
Der folgende Artikel basiert auf Beiträgen von
Gunnar Bittersmann und
Jürgen Berkemeier im SELFHTML Forum. Vielen Dank an die beiden.
Um Zahlenbereiche zu bezeichnen, verwendet man in der Alltagssprache »von 5 bis 10« oder »zwischen 5 und 10«. Allerdings ist nicht immer eindeutig, was damit gemeint ist und ob die Zahlen am Anfang und Ende noch dazugehören. Daher verwendet dieser Artikel eine gängige mathematische Schreibweise für Zahlenbereiche, sogenannte
Intervalle. Damit lassen sich die Sachverhalte rund um die Erzeugung von Zufallszahlen kompakter beschreiben:
Dieser Unterschied wird uns noch mehrfach beschäftigen, daher sollten Sie darauf achten, in welche Richtung die letzte Klammer zeigt. Diese bestimmt, ob die Zahl, die die Obergrenze bildet, noch zum Intervall dazugehört oder nicht.
Math.random()
Math.random() liefert gemäß dem ECMAScript-Standard eine Pseudo-Zufallszahl aus dem Intervall [0;1[, das heißt größer gleich 0 und kleiner als 1. Es handelt sich um eine
rationale Zahl, zum Beispiel 0.06631505941813465.
Nun will man in JavaScript äußerst selten eine Zufallszahl aus dem Intervall [0;1[, sondern aus [x;y]. x und y stehen für beliebige Werten wie zum Beispiel 5 und 10. Es gilt also, die von Math.random() gelieferten Wert auf eine anderes Intervall abzubilden.
Der erste Versuch könnte so aussehen:
var min = 5; var max = 10; var x = (Math.random() * (max - min)) + min;
Dies liefert eine rationale Zahl x aus dem Intervall [5;10[.
Dies erfüllt zwei übliche Wünsche nicht:
Math.round()Die übliche Lösung, die diese beiden Anforderungen zunächst erfüllt, sieht so aus:
var x = Math.round(Math.random() * (max - min)) + min;
Math.round() rundet kaufmännisch, das heißt, 5.2 wird 5, 7.2 wird 7 und 9.8 wird 10. Damit bekommen wir eine natürliche Zahl aus dem Intervall [5;10].
Diese Lösung findet sich zwar überall im Web, aber sie ist äußerst problematisch und fehlerhaft.
Will man eine zufällige Zahl aus der Menge { 5, 6, 7, 8, 9, 10 } ziehen, so soll jede Zahl mit gleicher Wahrscheinlichkeit gezogen werden. Der Wahrscheinlichkeitswert einer jeden Zahl aus dieser Menge mit sechs Zahlen wäre 1/6 (ein Sechstel). Das kennen wir von einem sechsseitigen Würfel.
Die obige Lösung mit Math.round() sorgt aber nicht für eine gleichmäßige Wahrscheinlichkeit. Die Zahl 5 wird nicht gleich häufig gezogen wie die Zahl 6.
Der Teilterm Math.random() * (10 - 5) berechnet zunächst eine zufällige rationale Zahl aus dem Intervall [0;5[. Diese Verteilung ist noch gleichmäßig. Das heißt, jede Zahl in diesem Intervall wird mit gleicher Wahrscheinlichkeit gezogen.
Bei der anschließenden Rundung muss nun bedacht werden, welcher Teilintervall zu welchem ganzzahligen Wert führt. Diese Rundung von Zahlen und die anschließende Addition des Minimalwertes soll folgende Tabelle veranschaulichen.
a = Math.random() * (10 - 5) |
[0; 0.5[ | [0.5; 1[ | [1; 1.5[ | [1.5; 2[ | [2; 2.5[ | [2.5; 3[ | [3; 3.5[ | [3.5; 4[ | [4; 4.5[ | [4.5; 5[ |
|---|---|---|---|---|---|---|---|---|---|---|
b = Math.round(a) |
0 | 1 | 2 | 3 | 4 | 5 | ||||
c = b + 5 |
5 | 6 | 7 | 8 | 9 | 10 | ||||
| Wahrscheinlichkeit | 1/10 | 1/5 | 1/5 | 1/5 | 1/5 | 1/10 | ||||
Die Tabelle ist so zu lesen: Der besagte Teilterm liefert eine Zufallszahl a aus dem Intervall [0;5[. Dieser ist in 0.5-er Teilintervallen in Spalten aufgeteilt.
Der springende Punkt ist nun, dass die Rundung die Zahlen aus dem Intevall [0; 0.5[ auf 0 abrundet. Alle Zahlen aus dem Intervall [0.5; 1.5[ werden hingegen auf 1 auf- bzw. abgerundet. Die Menge der Zufallszahlen, die gerundet 1 ergeben, ist also doppelt so groß wie die Menge der Zahlen, die gerundet 0 ergeben. Dasselbe Problem liegt am Ende vor: Lediglich die Zahlen aus dem Intervall [4.5; 5[ ergeben gerundet 5.
Der Effekt dieser Rundung ist eine ungleiche Verteilung der Wahrscheinlichkeiten. Die Zahlen 6, 7, 8 und 9 werden jeweils mit doppelter Wahrscheinlichkeit gezogen als die Zahlen 5 und 10. Die beiden Zahlen am Anfang und am Ende der Zahlenmenge, aus der zufällig gezogen werden soll, sind also benachteiligt in der Ziehung.
Math.floor()Lange Rede, kurzer Sinn: Ein anderes Verfahren ist nötig, damit jede Zahl mit gleicher Wahrscheinlichkeit gezogen wird. Eine Rundung ist zwar weiterhin nötig, aber eine, die immer ein Intervall der Länge 1 auf eine ganze Zahl abbildet.
Diese Aufgabe erfüllt
Math.floor(). Math.floor() rundet jede Zahl auf die nächstniedrige ganze Zahl ab. Die Berechnung der Zufallszahl wird entsprechend geändert:
var min = 5; var max = 10; var x = Math.floor(Math.random() * (max - min)) + min;
Dies gibt uns eine zufällige natürliche Zahl aus dem Intervall [5;10[. Dies fällt hinter die Lösung mit Math.round() in dem Punkt zurück, dass die maximale Zahl 10 wieder ausgespart wird. Daher wird der Term folgendermaßen ergänzt:
var x = Math.floor(Math.random() * (max - min + 1)) + min;
Die korrekte Funktionsweise wird deutlich, wenn wir die gleiche Tabelle mit der verbesserten Lösung aufstellen:
a = Math.random() * (10 - 5 + 1) |
[0; 1[ | [1; 2[ | [2; 3[ | [3; 4[ | [4; 5[ | [5; 6[ |
|---|---|---|---|---|---|---|
b = Math.floor(a) |
0 | 1 | 2 | 3 | 4 | 5 |
c = b + 5 |
5 | 6 | 7 | 8 | 9 | 10 |
| Wahrscheinlichkeit | 1/6 | 1/6 | 1/6 | 1/6 | 1/6 | 1/6 |
Anmerkung: Die Benutzung von Math.round() bei der Erzeugung von zufälligen Ganzzahlen ist kein Übel an sich. Die Lösung mit Math.round() lässt sich abändern, sodass ebenfalls ein Intervall der Länge 1 auf eine ganze Zahl abgebildet wird und die Gleichverteilung gewährleistet ist. Dazu verschiebt man die Zahlen vor der Rundung um 0.5 nach links, also substrahiert 0.5:
var x = Math.round(Math.random() * (max - min + 1) - 0.5) + min;
Dies sei hier nur der Vollständigkeit erwähnt. Um Verwirrung bei verschiedenen Lösungen mit Math.round() zu vermeiden, wird hier die Umsetzung mit Math.floor() favorisiert.
Eine allgemeine Helferfunktion, die eine Zufallszahl aus dem Intervall [min;max] zurückgibt, könnte so aussehen:
function rand (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
Diese Funktion verhält sich wie rand() in der Programmiersprache PHP. Um eine zufällige ganze Zahl größer gleich 5 und kleiner gleich 10, also aus der Zahlenmenge { 5, 6, 7, 8, 9, 10 } zu ziehen, rufen wir rand(5, 10) auf.
Bei 120 000 Ziehungen sollte jede Zahl aus der Menge { 5, 6, 7, 8, 9, 10 } im statistischen Mittel 20 000 mal gezogen werden, denn der Wahrscheinlichkeitswert liegt bei 1/6, also 0.16̅ (Periode 6, das heißt 0,16666... usw.). Klicken Sie auf »Berechnung starten«, um die beiden Lösungen zu vergleichen. Die angezeigten Wahrscheinlichkeiten sind gerundet. Sie können den Test auch mehrfach durchführen, um genauere Resultate zu bekommen. Sie werden das Ungleichgewicht bei den Zahlen 5 und 10 auf der Seite der Lösung mit Math.round() feststellen.
| Math.round() | Math.floor() | |||
|---|---|---|---|---|
| Zahl | Ziehungen | Wahrscheinlichkeit | Ziehungen | Wahrscheinlichkeit |
© 2008
Impressum, für diese Seite:
zapperlott@gmail.com