Menu

Zufallszahlen in C: rand, srand und eine Zahl in einem Bereich erzeugen

Wie man in C mit rand() und RAND_MAX Zufallszahlen erzeugt, warum man genau einmal mit srand(time(NULL)) initialisiert, das Abbilden in einen Bereich mit % und die kleine Verzerrung dabei, zufällige Gleitkommazahlen und reproduzierbare Folgen.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Cs Zufallszahlen kommen aus zwei Funktionen in <stdlib.h>: rand(), das den nächsten Wert erzeugt, und srand(), das den Startpunkt festlegt. Sie sind nicht wirklich zufällig - sie sind eine pseudozufällige Folge, deterministisch aus einem Startwert berechnet - was für Kryptografie eine Einschränkung und für Tests eine Stärke ist.

rand() und RAND_MAX

rand() liefert ein int irgendwo zwischen 0 und RAND_MAX, einschließlich. RAND_MAX ist ein Makro, das garantiert mindestens 32767 beträgt; unter Linux und macOS ist es 2147483647.

Führe das zweimal aus. Die Zahlen sind beide Male identisch - und das ist kein Bug.

Initialisieren mit srand

Ohne Aufruf von srand verhält sich die Folge, als hättest du srand(1) aufgerufen. Gleicher Startwert, gleiche Folge, in jedem Lauf. Um pro Lauf andere Zahlen zu bekommen, initialisiere mit etwas, das sich ändert - üblicherweise der aktuellen Zeit:

time(NULL) aus <time.h> liefert die Sekunden seit dem Beginn von 1970, jeder Lauf bekommt also einen anderen Startwert. Der Cast nach unsigned int bringt eine Warnung über das Verengen von time_t zum Schweigen.

Drei Regeln zum Initialisieren, die alle falsch gemacht werden:

Initialisiere genau einmal, am Anfang von main. srand vor jedem rand() aufzurufen ist das klassische Anti-Muster - in einer Schleife, die in unter einer Sekunde fertig ist, liefert time(NULL) bei jeder Iteration denselben Wert, du initialisierst also mit derselben Zahl neu, und rand() liefert jedes Mal denselben ersten Wert. Die Ausgabe ist eine Spalte identischer „Zufalls"-Zahlen.

Initialisiere nicht neu, um die Zufälligkeit zu „verbessern". Die Qualität des Generators kommt daher, dass sein interner Zustand fortschreitet; diesen Zustand zurückzusetzen wirft die Folge weg.

time(NULL) hat Sekundenauflösung. Zwei in derselben Sekunde gestartete Programme bekommen dieselbe Folge. Für ein Spiel ist das in Ordnung und für alles falsch, wo Unabhängigkeit zählt.

Eine Zahl in einem Bereich

Das übliche Idiom benutzt den Restoperator:

rand() % n            /* 0 bis n-1 */
rand() % n + min      /* min bis min+n-1 */

Um min bis max einschließlich zu erhalten, ist die Anzahl möglicher Werte max - min + 1:

Beim + 1 wohnen die Off-by-one-Fehler. rand() % 6 gibt 0 bis 5, ein Würfelwurf ist also rand() % 6 + 1. rand() % 7 + 1 zu schreiben, um „die 6 einzuschließen", gibt dir einen siebenseitigen Würfel.

Die ehrliche Anmerkung zur Modulo-Verzerrung

rand() % n ist nicht perfekt gleichverteilt, sofern n die Zahl RAND_MAX + 1 nicht genau teilt.

Denk in kleinen Zahlen. Wäre RAND_MAX gleich 9 - rand() liefert also 0 bis 9, zehn gleich wahrscheinliche Werte -, dann bildet rand() % 3 die Werte 0, 3, 6, 9 auf 0 ab; 1, 4, 7 auf 1; und 2, 5, 8 auf 2. Das Ergebnis 0 entsteht auf vier von zehn Wegen, die Ergebnisse 1 und 2 auf je drei. Null ist 33 % wahrscheinlicher.

Dieselbe Schieflage existiert beim echten RAND_MAX, nur weit kleiner: Die übrigen Werte sind die ersten (RAND_MAX + 1) % n Ausgänge, von denen jeder eine zusätzliche Chance aus rund 2,1 Milliarden gewinnt. Für einen Würfelwurf, ein gemischtes Kartenspiel oder eine Simulation ist das nicht messbar - nimm % und mach weiter.

Wo es doch zählt - statistische Arbeit, alles Sicherheitsrelevante -, verwirf die übrigen Werte, statt sie hineinzufalten:

Die Schleife wirft den kleinen Wertebereich weg, der die Schieflage verursachen würde, und zieht erneut. Sie terminiert schnell - der verworfene Ausschnitt ist ein verschwindender Bruchteil des Ganzen.

Für wirklich sicherheitsrelevanten Zufall ist rand() bei jedem Maß an Sorgfalt das falsche Werkzeug: Nimm arc4random_buf unter macOS und BSD, getrandom() unter Linux oder BCryptGenRandom unter Windows.

Zufällige Gleitkommazahlen

Teile durch RAND_MAX, um in [0.0, 1.0] zu landen, und skaliere dann:

Der Cast bei (double) rand() ist unverzichtbar. Ohne ihn ist rand() / RAND_MAX eine Ganzzahldivision und ergibt fast immer 0 und mit der Chance von eins zu zwei Milliarden 1 - ein Bug, der aussieht wie „meine zufälligen doubles sind alle null". Siehe Typumwandlung für den Grund.

Reproduzierbare Folgen

Ein fester Startwert liefert in jedem Lauf die identische Folge, und genau das willst du für einen Test, eine Debugging-Sitzung oder ein Spiel mit teilbaren Levelcodes:

Der Startwert 42 erzeugt jedes Mal dieselben fünf Zahlen, in diesem Lauf und in jedem anderen mit derselben Bibliothek. Diese Reproduzierbarkeit ist der Grund, warum eine Simulation den Startwert wählbar machen sollte: normal mit der Uhr laufen lassen, einen festen Startwert übergeben, wenn du einen Bug nachstellst.

Ein Vorbehalt: Die Folge zu einem gegebenen Startwert ist nicht portabel. Verschiedene C-Bibliotheken benutzen verschiedene Generatoren, Startwert 42 unter glibc und Startwert 42 unter Windows liefern also verschiedene Zahlen. Reproduzierbar auf einer Maschine, nicht über Maschinen hinweg.

Ein Würfelspiel

Alles zusammen - einmal initialisieren, ein Helfer für den Bereich und ein Array, das die Ergebnisse zählt:

Das Histogramm sollte bei 7 seinen Gipfel haben und Richtung 2 und 12 auslaufen - es gibt sechs Wege, eine 7 zu würfeln, und je einen für 2 oder 12. Ein Generator, der hier eine flache Verteilung erzeugt, wäre kaputt.

Zwei verwandte Seiten: Die Standardbibliothek kartiert den Rest von <stdlib.h>, und Mathematikfunktionen behandelt <math.h>, das du willst, sobald Zufallswerte in echte Berechnungen einfließen.

Häufig gestellte Fragen

Wie erzeuge ich in C eine Zufallszahl?

Binde <stdlib.h> ein, initialisiere einmal am Anfang von main mit srand((unsigned) time(NULL)) (das braucht <time.h>), und rufe dann für jeden Wert rand() auf. rand() liefert ein int zwischen 0 und RAND_MAX einschließlich.

Wie bekomme ich in C eine Zufallszahl zwischen zwei Werten?

Nimm rand() % (max - min + 1) + min. Für einen Würfelwurf zwischen 1 und 6 ist das rand() % 6 + 1. Das % n bildet das Ergebnis auf 0..n-1 ab, und min zu addieren verschiebt das Fenster - achte nur darauf, dass die Anzahl beide Enden einschließt, und genau das tut das + 1.

Warum gibt mein C-Programm jedes Mal dieselben Zufallszahlen aus?

Weil du srand nie aufgerufen hast. Ohne Startwert verhält sich rand(), als wäre es mit 1 initialisiert worden, jeder Lauf erzeugt also die identische Folge. Rufe einmal beim Programmstart srand((unsigned) time(NULL)) auf - einmal, nicht vor jedem rand()-Aufruf, was die Sache verschlimmern würde.

Was ist Modulo-Verzerrung bei der Erzeugung von Zufallszahlen?

rand() % n ist nur dann perfekt gleichverteilt, wenn n die Zahl RAND_MAX + 1 ohne Rest teilt. Sonst treten die ersten paar Werte über den ganzen Bereich einmal zusätzlich auf und sind damit ganz leicht wahrscheinlicher. Mit RAND_MAX bei 2147483647 und einem kleinen n liegt die Schieflage weit unter allem, was ein Spiel oder eine Simulation bemerkt, aber für Kryptografie oder Statistik nimm eine Verwerfungsschleife oder einen ordentlichen Generator.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S