srand () - warum nur einmal aufrufen?

76

Bei dieser Frage handelt es sich um einen Kommentar in dieser Frage. Empfohlene Methode zum Initialisieren von srand? Der erste Kommentar besagt, dass srand()dies in einer Anwendung nur EINMAL aufgerufen werden sollte. Wieso ist es so?

Lipika Deka
quelle
Versuchen Sie in einer Schleife, srand und dann rand anzurufen
Foo Bah
7
Siehe auch Dilberts Tour of Accounting .
Jonathan Leffler

Antworten:

109

Das hängt davon ab, was Sie erreichen wollen.

Die Randomisierung wird als eine Funktion durchgeführt, die einen Startwert hat, nämlich den Startwert .

Für denselben Startwert erhalten Sie also immer dieselbe Wertesequenz.

Wenn Sie versuchen, den Startwert jedes Mal festzulegen, wenn Sie einen zufälligen Wert benötigen und der Startwert dieselbe Zahl ist, erhalten Sie immer denselben "zufälligen" Wert.

Der Startwert wird normalerweise aus der aktuellen Zeit entnommen. time(NULL)Dies sind die Sekunden wie in . Wenn Sie also immer den Startwert festlegen, bevor Sie die Zufallszahl verwenden, erhalten Sie dieselbe Nummer, solange Sie die srand / rand-Kombination mehrmals in der gleiche Sekunde .

Um dieses Problem zu vermeiden, wird srand nur einmal pro Anwendung festgelegt, da es zweifelhaft ist, dass zwei der Anwendungsinstanzen in derselben Sekunde initialisiert werden, sodass jede Instanz dann eine andere Folge von Zufallszahlen hat.

Es besteht jedoch die geringe Möglichkeit, dass Sie Ihre App (insbesondere wenn es sich um eine kurze App oder ein Befehlszeilentool oder ähnliches handelt) mehrmals in einer Sekunde ausführen. Dann müssen Sie auf eine andere Art der Auswahl einer App zurückgreifen Startwert (es sei denn, Sie verwenden dieselbe Sequenz in verschiedenen Anwendungsinstanzen). Aber wie gesagt, das hängt von Ihrem Anwendungskontext ab.

Möglicherweise möchten Sie auch versuchen, die Genauigkeit auf Mikrosekunden zu erhöhen (um die Wahrscheinlichkeit des gleichen Startwerts zu minimieren). Erfordert ( sys/time.h):

struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);
Kornelije Petak
quelle
3
Randnotiz: gettimeofdayist in POSIX 2008 veraltet. Stattdessen werden Einführungen eingeführt, für clock_gettimedie möglicherweise eine Verknüpfung erforderlich ist -lrt. Es ist jedoch möglicherweise noch nicht auf vielen Plattformen verfügbar. Unter Linux ist das in Ordnung. Auf dem Mac denke ich, dass es noch nicht verfügbar ist. Unter Windows wird es wahrscheinlich nie verfügbar sein.
Shahbaz
1
t1.tv_usec ist ein langes int, und srand verwendet als Eingabe ein vorzeichenloses int. (Und ich bin gerade auf ein Problem
gestoßen,
24

Zufallszahlen sind eigentlich pseudozufällig. Zuerst wird ein Startwert festgelegt, von dem jeder Anruf randeine Zufallszahl erhält, und der interne Status wird geändert. Dieser neue Status wird beim nächsten randAufruf verwendet, um eine andere Nummer zu erhalten. Da zum Generieren dieser "Zufallszahlen" eine bestimmte Formel verwendet wird, wird beim Festlegen eines bestimmten Startwerts nach jedem Aufruf randdieselbe Nummer vom Aufruf zurückgegeben. Zum Beispiel srand (1234); rand ();wird der gleiche Wert zurückgegeben. Wenn Sie den Anfangszustand mit dem Startwert einmal initialisieren, werden genügend Zufallszahlen generiert, da Sie den internen Zustand nicht mit festlegen srand, wodurch die Wahrscheinlichkeit erhöht wird, dass die Zahlen zufällig sind.

Im Allgemeinen verwenden wir den time (NULL)zurückgegebenen Sekundenwert, wenn wir den Startwert initialisieren. Sagen wir, das srand (time (NULL));ist in einer Schleife. Dann kann die Schleife mehr als einmal in einer Sekunde iterieren. Daher gibt die Häufigkeit, mit der die Schleife innerhalb der Schleife bei einem zweiten randAufruf in der Schleife iteriert, dieselbe "Zufallszahl" zurück, was nicht erwünscht ist. Wenn Sie es einmal beim Programmstart initialisieren, wird der Startwert einmal festgelegt. Bei jedem randAufruf wird eine neue Nummer generiert und der interne Status geändert, sodass der nächste Aufruf randeine Nummer zurückgibt, die zufällig genug ist.

Zum Beispiel dieser Code von http://linux.die.net/man/3/rand :

static unsigned long next = 1;
/* RAND_MAX assumed to be 32767 */
int myrand(void) {
    next = next * 1103515245 + 12345;
    return((unsigned)(next/65536) % 32768);
}
void mysrand(unsigned seed) {
    next = seed;
}

Der interne Zustand nextwird als global deklariert. Bei jedem myrandAufruf wird der interne Status geändert, aktualisiert und eine Zufallszahl zurückgegeben. Jeder Aufruf von myrandhat einen anderen nextWert, daher gibt die Methode bei jedem Aufruf die unterschiedlichen Nummern zurück.

Schauen Sie sich die mysrandImplementierung an. Es wird einfach der Startwert festgelegt, an den Sie übergeben next. Wenn Sie den nextWert daher jedes Mal vor dem Aufruf gleich einstellen, randwird aufgrund der identischen Formel derselbe Zufallswert zurückgegeben, was nicht wünschenswert ist, da die Funktion zufällig ist.

Abhängig von Ihren Anforderungen können Sie den Startwert jedoch auf einen bestimmten Wert setzen, um bei jedem Lauf dieselbe "Zufallssequenz" zu generieren, beispielsweise für einen Benchmark oder andere.

Phoxis
quelle
Meinen Sie nicht (vorzeichenloser langer Startwert) für den Parameter von mysrand ()?
Jiminion
@Jiminion Dies ist ein Code-Snippet von man srand. Der Bereich reicht von 0 bis 32767 (unter der Annahme von RAND_MAX), was weit unter dem longBereich liegt. Die Zustandsvariable nextwird erstellt, longda die interne Multiplikation und Addition den Bereich von a überschreitet unsigned int. Danach wird das Ergebnis innerhalb des oben angegebenen Bereichs skaliert oder geändert. Obwohl Sie den Samen machen können long.
Phoxis
1
Beachten Sie, dass der C-Standard auch den angezeigten Codeausschnitt enthält.
Jonathan Leffler
11

Kurze Antwort: Anrufen srand()ist nicht wie "Würfeln" für den Zufallszahlengenerator. Es ist auch nicht so, als würde man ein Kartenspiel mischen. Wenn überhaupt, ist es eher so, als würde man nur ein Kartenspiel schneiden.

Stellen Sie sich das so vor. rand()Angebote aus einem großen Kartenspiel, und jedes Mal, wenn Sie es aufrufen, müssen Sie nur die nächste Karte oben auf dem Kartenspiel auswählen, den Wert angeben und diese Karte wieder unten im Kartenspiel ablegen. (Ja, das bedeutet, dass sich die "zufällige" Sequenz nach einer Weile wiederholt. Es ist jedoch ein sehr großes Deck: normalerweise 4.294.967.296 Karten.)

Außerdem wird jedes Mal, wenn Ihr Programm ausgeführt wird, ein brandneues Kartenspiel im Spieleladen gekauft, und jedes brandneue Kartenspiel hat immer die gleiche Reihenfolge. Wenn Sie also nichts Besonderes tun, erhält Ihr Programm jedes Mal, wenn es ausgeführt wird, genau die gleichen "Zufallszahlen" zurück rand().

Nun könnte man sagen: "Okay, wie mische ich das Deck?" Und die Antwort - zumindest was randund srandbetrifft - ist, dass es keine Möglichkeit gibt, das Deck zu mischen.

Was macht srandalso? Basierend auf der Analogie, die ich hier aufgebaut habe, ist das Anrufen srand(n)im Grunde so, als würde man sagen: "Schneide die nDeckkarten von oben ab". Aber warten Sie noch eine Sache: Es beginntn tatsächlich mit einem anderen brandneuen Deck und schneidet die Karten von oben ab .

Also , wenn Sie anrufen srand(n), rand(), srand(n), rand(), ..., mit dem gleichen njedes Mal, werden Sie nicht nur eine nicht sehr Zufallssequenz erhalten, die Sie erhalten tatsächlich die gleiche Zahl wieder aus rand()jeder Zeit. (Wahrscheinlich nicht die gleiche Nummer, die Sie übergeben haben srand, aber immer wieder dieselbe Nummer rand.)

Das Beste, was Sie tun können, ist, das Deck einmal zu schneiden , dh srand()einmal zu Beginn Ihres Programms mit einem neinigermaßen zufälligen Aufruf aufzurufen , sodass Sie jedes Mal an einer anderen zufälligen Stelle im großen Deck beginnen Programm läuft. Mit rand(), das ist wirklich das Beste, was Sie tun können.

[PS Ja, ich weiß, wenn Sie im wirklichen Leben ein brandneues Kartenspiel kaufen, ist es normalerweise in der richtigen Reihenfolge, nicht in zufälliger Reihenfolge. Damit die Analogie hier funktioniert, stelle ich mir vor, dass jedes Deck, das Sie im Game-Shop kaufen, in einer scheinbar zufälligen Reihenfolge vorliegt, aber genau dieselbe scheinbar zufällige Reihenfolge wie jedes andere Kartenspiel, das Sie in demselben Shop kaufen. So ähnlich wie die identisch gemischten Kartenspiele, die sie in Brückenturnieren verwenden.]

Steve Summit
quelle
Hervorragende Erklärung Steve.
DrunkenMaster
8

Der Grund dafür ist, dass srand()der Anfangszustand des Zufallsgenerators festgelegt wird und alle vom Generator erzeugten Werte nur dann "zufällig genug" sind, wenn Sie den Zustand dazwischen nicht selbst berühren.

Zum Beispiel könnten Sie tun:

int getRandomValue()
{
    srand(time(0));
    return rand();
}

und wenn Sie diese Funktion dann wiederholt aufrufen, sodass time()bei benachbarten Aufrufen dieselben Werte zurückgegeben werden, wird nur derselbe Wert generiert - das ist beabsichtigt.

scharfer Zahn
quelle
3

Eine einfachere Lösung zum srand()Generieren verschiedener Startwerte für Anwendungsinstanzen, die in derselben Sekunde ausgeführt werden, ist wie zu sehen.

srand(time(NULL)-getpid());

Diese Methode macht Ihren Samen sehr zufällig, da es keine Möglichkeit gibt zu erraten, wann Ihr Thread gestartet wurde und die PID auch anders sein wird.

Achoora
quelle
2

srand setzt den Pseudozufallszahlengenerator. Wenn Sie es mehrmals aufrufen, setzen Sie das RNG erneut ein. Und wenn Sie es mit demselben Argument aufrufen, wird dieselbe Sequenz neu gestartet.

Um es zu beweisen, wenn Sie etwas Einfaches tun wie:

#include <cstdlib>
#include <cstdio>
int main() {
for(int i = 0; i != 100; ++i) {
        srand(0);
        printf("%d\n", rand());
    }
}

Sie sehen die gleiche Nummer 100 Mal gedruckt.

Foo Bah
quelle
2
Die Frage betrifft C, nicht C ++.
Spikatrix
0

1 \ Scheint jedes Mal, wenn rand () ausgeführt wird, einen neuen Startwert für den nächsten rand () zu setzen.

2 \ Wenn srand () mehrmals ausgeführt wird, besteht das Problem darin, dass der nächste rand () derselbe wie der rand () direkt nach dem vorherigen ist, wenn die beiden in einer Sekunde ausgeführt werden (die Zeit (NULL) ändert sich nicht) srand ().

imoc
quelle
Der Hauptpunkt ist, dass das srand()mehrmalige Initialisieren mit demselben Startwert zu identischen Werten führt, die von zurückgegeben werden rand().
König Drosselbart