Das grundlegendste Problem Ihrer Testanwendung besteht darin, dass Sie srand
einmal anrufen und dann einmal anrufen rand
und beenden.
Der ganze Sinn der srand
Funktion besteht darin, die Folge von Pseudozufallszahlen mit einem zufälligen Startwert zu initialisieren .
Es bedeutet , dass , wenn Sie übergeben den gleichen Wert zu srand
in zwei verschiedenen Anwendungen (mit der gleichen srand
/ rand
Umsetzung) , dann erhalten Sie genau die gleiche Sequenz von rand()
Werten nach , dass in beiden Anwendungen lesen.
In Ihrer Beispielanwendung besteht die Pseudozufallssequenz jedoch nur aus einem Element - dem ersten Element einer Pseudozufallssequenz, die aus einem Startwert erzeugt wird, der der aktuellen second
Genauigkeitszeit entspricht. Was erwarten Sie dann bei der Ausgabe?
Wenn Sie die Anwendung in derselben Sekunde ausführen - Sie verwenden denselben Startwert -, ist Ihr Ergebnis natürlich das gleiche (wie Martin York bereits in einem Kommentar zur Frage erwähnt hat).
Eigentlich sollten Sie einmal anrufen srand(seed)
und dann rand()
viele Male anrufen und diese Sequenz analysieren - sie sollte zufällig aussehen.
BEARBEITEN:
Oh ich verstehe. Anscheinend reicht eine verbale Beschreibung nicht aus (vielleicht Sprachbarriere oder so ... :)).
OK. Altmodisches C-Codebeispiel basierend auf denselben srand()/rand()/time()
Funktionen, die in der Frage verwendet wurden:
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main(void)
{
unsigned long j;
srand( (unsigned)time(NULL) );
for( j = 0; j < 100500; ++j )
{
int n;
/* skip rand() readings that would make n%6 non-uniformly distributed
(assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
{ /* bad value retrieved so get next one */ }
printf( "%d,\t%d\n", n, n % 6 + 1 );
}
return 0;
}
^^^ DASS Sequenz aus einem einzigen Durchlauf des Programms soll zufällig aussehen.
EDIT2:
Bei Verwendung der C- oder C ++ - Standardbibliothek ist es wichtig zu verstehen, dass es derzeit keine einzige Standardfunktion oder -klasse gibt, die definitiv zufällige Daten erzeugt (garantiert durch den Standard). Das einzige Standardwerkzeug, das sich diesem Problem nähert, ist std :: random_device , das leider immer noch keine Garantie für die tatsächliche Zufälligkeit bietet.
Abhängig von der Art der Anwendung sollten Sie zunächst entscheiden, ob Sie wirklich zufällige (unvorhersehbare) Daten benötigen. Ein bemerkenswerter Fall, in dem Sie mit Sicherheit echte Zufälligkeit benötigen, ist die Informationssicherheit - z. B. das Generieren symmetrischer Schlüssel, asymmetrischer privater Schlüssel, Salt-Werte, Sicherheitstoken usw.
Zufallszahlen mit Sicherheitsstufe sind jedoch eine separate Branche, die einen separaten Artikel wert ist.
In den meisten Fällen ist der Pseudo-Zufallszahlengenerator ausreichend - z. B. für wissenschaftliche Simulationen oder Spiele. In einigen Fällen ist sogar eine konsistent definierte Pseudozufallssequenz erforderlich. In Spielen können Sie beispielsweise zur Laufzeit genau dieselben Karten erstellen, um zu vermeiden, dass viele Daten gespeichert werden.
Die ursprüngliche Frage und die wiederkehrende Vielzahl identischer / ähnlicher Fragen (und sogar viele fehlgeleitete "Antworten" darauf) zeigen, dass es in erster Linie wichtig ist, Zufallszahlen von Pseudozufallszahlen zu unterscheiden UND zu verstehen, in welcher Pseudozufallszahlenfolge sie sich befinden der erste Ort UND um zu erkennen, dass Pseudozufallszahlengeneratoren NICHT so verwendet werden, wie Sie echte Zufallszahlengeneratoren verwenden könnten.
Intuitiv, wenn Sie eine Zufallszahl anfordern - das zurückgegebene Ergebnis sollte nicht von zuvor zurückgegebenen Werten abhängen und sollte nicht davon abhängen, ob jemand zuvor etwas angefordert hat, und sollte nicht davon abhängen, in welchem Moment und durch welchen Prozess und von welchem Computer und von welchem Generator und in welche Galaxie wurde es angefordert. Das ist es, was das Wort "zufällig" schließlich bedeutet - unvorhersehbar und unabhängig von irgendetwas -, sonst ist es nicht mehr zufällig, oder? Mit dieser Intuition ist es nur natürlich, im Internet nach Zaubersprüchen zu suchen, um eine solche Zufallszahl in einem möglichen Kontext zu erhalten.
^^^ DIESE Art von intuitiven Erwartungen ist SEHR FALSCH und schädlich in allen Fällen, in denen Pseudozufallszahlengeneratoren verwendet werden - obwohl sie für echte Zufallszahlen angemessen sind.
Während der sinnvolle Begriff "Zufallszahl" existiert, gibt es keine "Pseudozufallszahl". Ein Pseudo-Zufallszahlengenerator erzeugt tatsächlich Pseudozufallszahlenfolge .
Wenn Experten über die Qualität von PRNG sprechen, sprechen sie tatsächlich über statistische Eigenschaften der generierten Sequenz (und ihrer bemerkenswerten Teilsequenzen). Wenn Sie beispielsweise zwei PRNGs von hoher Qualität kombinieren, indem Sie beide nacheinander verwenden - Sie können eine schlechte resultierende Sequenz erzeugen - obwohl sie jeweils gute Sequenzen erzeugen (diese beiden guten Sequenzen können einfach miteinander korrelieren und somit schlecht kombinieren).
Die pseudozufällige Sequenz ist in der Tat immer deterministisch (vorgegeben durch ihren Algorithmus und ihre Anfangsparameter), dh es ist tatsächlich nichts Zufälliges daran.
Speziell rand()
/ srand(s)
Funktionspaar stellen eine singuläre, nicht threadsichere (!) Pseudozufallszahlenfolge pro Prozess bereit, die mit einem implementierungsdefinierten Algorithmus generiert wird. Die Funktion rand()
erzeugt Werte im Bereich [0, RAND_MAX]
.
Zitat aus dem C11-Standard:
Die srand
Funktion verwendet das Argument als Startwert für eine neue Folge von Pseudozufallszahlen, die bei nachfolgenden Aufrufen an zurückgegeben werden sollen rand
. Wenn
srand
dann mit demselben Startwert aufgerufen wird, muss die Folge von Pseudozufallszahlen wiederholt werden. Wenn rand
vor einem Aufruf aufgerufen wird srand
, wird dieselbe Sequenz generiert wie beim srand
ersten Aufruf mit einem Startwert von 1.
Viele Menschen erwarten vernünftigerweise, dass rand()
dies eine Folge von halbunabhängigen, gleichmäßig verteilten Zahlen im Bereich 0
von erzeugen würde RAND_MAX
. Nun, es sollte auf jeden Fall (ansonsten ist es nutzlos), aber leider erfordert dies nicht nur der Standard - es gibt sogar einen expliziten Haftungsausschluss, der besagt, dass "es keine Garantien für die Qualität der erzeugten Zufallssequenz gibt" . In einigen historischen Fällen rand
/ srand
Implementierung war von sehr schlechter Qualität in der Tat. Obwohl es in modernen Implementierungen höchstwahrscheinlich gut genug ist - aber das Vertrauen ist gebrochen und nicht einfach wiederherzustellen. Abgesehen davon, dass es nicht threadsicher ist, ist seine sichere Verwendung in Multithread-Anwendungen schwierig und begrenzt (immer noch möglich - Sie können sie nur von einem dedizierten Thread aus verwenden).
Die neue Klassenvorlage std :: mersenne_twister_engine <> (und ihre praktischen typedefs - std::mt19937
/ std::mt19937_64
mit einer guten Kombination von Vorlagenparametern) bietet einen im C ++ 11-Standard definierten Pseudozufallszahlengenerator pro Objekt . Mit denselben Vorlagenparametern und denselben Initialisierungsparametern generieren verschiedene Objekte auf jedem Computer in jeder Anwendung, die mit einer C ++ 11-kompatiblen Standardbibliothek erstellt wurde, genau dieselbe Ausgabesequenz pro Objekt. Der Vorteil dieser Klasse ist die vorhersagbar hohe Ausgabesequenz und die vollständige Konsistenz über alle Implementierungen hinweg.
Es gibt auch mehr PRNG-Engines, die im C ++ 11-Standard definiert sind - std :: linear_congruential_engine <> ( srand/rand
in einigen Implementierungen der C-Standardbibliothek historisch als Algorithmus für faire Qualität verwendet) und std :: subtract_with_carry_engine <> . Sie erzeugen auch vollständig definierte parameterabhängige Ausgabesequenzen pro Objekt.
Beispiel für modernen C ++ 11-Ersatz für den veralteten C-Code oben:
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
// seed value is designed specifically to make initialization
// parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
// different across executions of application
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
for( unsigned long j = 0; j < 100500; ++j )
/* ^^^Yes. Generating single pseudo-random number makes no sense
even if you use std::mersenne_twister_engine instead of rand()
and even when your seed quality is much better than time(NULL) */
{
std::mt19937::result_type n;
// reject readings that would make n%6 non-uniformly distributed
while( ( n = gen() ) > std::mt19937::max() -
( std::mt19937::max() - 5 )%6 )
{ /* bad value retrieved so get next one */ }
std::cout << n << '\t' << n % 6 + 1 << '\n';
}
return 0;
}
Die Version des vorherigen Codes, die std :: uniform_int_distribution <> verwendet
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
std::uniform_int_distribution<unsigned> distrib(1, 6);
for( unsigned long j = 0; j < 100500; ++j )
{
std::cout << distrib(gen) << ' ';
}
std::cout << '\n';
return 0;
}