<random> generiert unter Linux dieselbe Nummer, jedoch nicht unter Windows

90

Der folgende Code soll eine Liste von fünf Pseudozufallszahlen im Intervall [1.100] generieren. Ich setze das default_random_enginemit time(0), was die Systemzeit in Unix-Zeit zurückgibt . Wenn ich dieses Programm unter Windows 7 mit Microsoft Visual Studio 2013 kompiliere und ausführe, funktioniert es wie erwartet (siehe unten). Wenn ich dies in Arch Linux mit dem g ++ - Compiler mache, verhält es sich jedoch seltsam.

Unter Linux werden jedes Mal 5 Zahlen generiert. Die letzten 4 Zahlen sind bei jeder Ausführung unterschiedlich (wie es häufig der Fall ist), aber die erste Zahl bleibt gleich.

Beispielausgabe von 5 Ausführungen unter Windows und Linux:

      | Windows:       | Linux:        
---------------------------------------
Run 1 | 54,01,91,73,68 | 25,38,40,42,21
Run 2 | 46,24,16,93,82 | 25,78,66,80,81
Run 3 | 86,36,33,63,05 | 25,17,93,17,40
Run 4 | 75,79,66,23,84 | 25,70,95,01,54
Run 5 | 64,36,32,44,85 | 25,09,22,38,13

Hinzu kommt, dass diese erste Zahl unter Linux regelmäßig um eins erhöht wird. Nachdem ich die obigen Ausgaben erhalten hatte, wartete ich ungefähr 30 Minuten und versuchte erneut festzustellen, dass sich die erste Zahl geändert hatte und nun immer als 26 generiert wurde. Sie wurde in regelmäßigen Abständen um 1 erhöht und liegt nun bei 32. Es scheint zu entsprechen mit dem sich ändernden Wert von time(0).

Warum ändert sich die erste Zahl selten über Läufe hinweg und erhöht sich dann, wenn dies der Fall ist, um 1?

Der Code. Es druckt die 5 Zahlen und die Systemzeit sauber aus:

#include <iostream>
#include <random>
#include <time.h>

using namespace std;

int main()
{
    const int upper_bound = 100;
    const int lower_bound = 1;

    time_t system_time = time(0);    

    default_random_engine e(system_time);
    uniform_int_distribution<int> u(lower_bound, upper_bound);

    cout << '#' << '\t' << "system time" << endl
         << "-------------------" << endl;

    for (int counter = 1; counter <= 5; counter++)
    {
        int secret = u(e);
        cout << secret << '\t' << system_time << endl;
    }   

    system("pause");
    return 0;
}
Amin Mesbah
quelle
3
Was ist sizeof(time_t)gegen sizeof(default_random_engine::result_type)?
Mark Ransom
3
Beachten Sie, dass default_random_enginedies auf diesen beiden Plattformen völlig unterschiedlich ist.
TC
1
Es kann übrigens immer noch zufällig sein.
Alec Teal
5
Durchläuft jeder Programmierer eine Phase, in der er der Meinung ist, dass Zeit ein guter Startwert für einen Zufallszahlengenerator ist?
OldFart
6
@OldFart Ja, es heißt Akademie.
Casey

Antworten:

141

Folgendes ist los:

  • default_random_enginein libstdc ++ (GCCs Standardbibliothek) ist minstd_rand0dies eine einfache lineare kongruente Engine:

    typedef linear_congruential_engine<uint_fast32_t, 16807, 0, 2147483647> minstd_rand0;
  • Die Art und Weise, wie diese Engine Zufallszahlen generiert, ist x i + 1 = (16807x i + 0) mod 2147483647.

  • Wenn sich die Samen um 1 unterscheiden, unterscheidet sich die erste generierte Zahl daher meistens um 16807.

  • Die Reichweite dieses Generators beträgt [1, 2147483646]. Libstdc ++ uniform_int_distributionordnet es im Wesentlichen einer Ganzzahl im Bereich [1, 100] zu: Generieren Sie eine Zahl n. Wenn die Anzahl nicht größer als 2147483600 ist, kehren Sie zurück (n - 1) / 21474836 + 1. Andernfalls versuchen Sie es erneut mit einer neuen Nummer.

    Es sollte leicht zu erkennen sein, dass in den allermeisten Fällen zwei ns, die sich nur um 16807 unterscheiden, nach diesem Verfahren die gleiche Zahl in [1, 100] ergeben. Tatsächlich würde man erwarten, dass sich die generierte Zahl etwa alle 21474836/16807 = 1278 Sekunden oder 21,3 Minuten um eins erhöht, was ziemlich gut mit Ihren Beobachtungen übereinstimmt.

MSVCs default_random_engineist mt19937, die dieses Problem nicht haben.

TC
quelle
36
Ich frage mich, was die Entwickler der Standardbibliothek von GCC dazu gebracht haben, einen so schrecklichen Standard zu wählen.
CodesInChaos
13
@CodesInChaos Ich weiß nicht, ob es nicht verwandt ist, aber die MacOS / iOS-Toolchain verwendet auch die gleiche schreckliche Zufalls-Engine, sodass rand()% 7 immer 0
zurückgibt
7
@ LưuVĩnhPhúc Nicht zu reparieren rand()ist etwas verständlich (es ist hoffnungsloser Legacy-Mist). Die Verwendung eines Shit-Tier-PRNG für etwas Neues ist unentschuldbar. Ich würde dies sogar als Standardverletzung betrachten, da der Standard verlangt, "zumindest ein akzeptables Motorverhalten für eine relativ gelegentliche, unsachgemäße und / oder leichte Verwendung bereitzustellen". Dies bietet diese Implementierung nicht, da sie selbst für triviale Anwendungsfälle wie Ihr rand % 7Beispiel katastrophal fehlschlägt .
CodesInChaos
2
@CodesInChaos Warum ist das Fixieren nicht rand()genau verständlich? Ist es nur, weil niemand daran gedacht hätte?
user253751
2
@immibis Die API ist so kaputt, dass Sie mit einem unabhängigen Ersatz, der alle Probleme behebt, besser dran sind. 1) Das Ersetzen des Algorithmus wäre eine bahnbrechende Änderung, sodass Sie wahrscheinlich einen Kompatibilitätsschalter für ältere Programme benötigen würden. 2) Der Samen von srandist zu klein, um leicht einzigartige Samen zu erzeugen. 3) Es gibt eine Ganzzahl mit einer durch die Implementierung definierten Obergrenze zurück, die der Aufrufer irgendwie auf eine Zahl im gewünschten Bereich reduzieren muss. Dies ist bei richtiger Ausführung mehr Arbeit als das Schreiben eines Ersatzes mit einer vernünftigen API für rand()4) Es verwendet den globalen veränderlichen Status
CodesInChaos
30

Die std::default_random_engineImplementierung ist definiert. Verwenden Sie std::mt19937oder std::mt19937_64stattdessen.

Wenn std::timedie ctimeFunktionen nicht sehr genau sind, verwenden Sie <chrono>stattdessen die in der Kopfzeile definierten Typen :

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    const int upper_bound = 100;
    const int lower_bound = 1;

    auto t = std::chrono::high_resolution_clock::now().time_since_epoch().count();

    std::mt19937 e;
    e.seed(static_cast<unsigned int>(t)); //Seed engine with timed value.
    std::uniform_int_distribution<int> u(lower_bound, upper_bound);

    std::cout << '#' << '\t' << "system time" << std::endl
    << "-------------------" << std::endl;

    for (int counter = 1; counter <= 5; counter++)
    {
        int secret = u(e);

        std::cout << secret << '\t' << t << std::endl;
    }   

    system("pause");
    return 0;
}
Casey
quelle
3
Ist es wünschenswert, eine genauere Zeit zu verwenden, wenn ein Pseudozufallsvariablengenerator gesetzt wird? Vielleicht ist das naiv, aber es scheint, als wäre Ungenauigkeit fast wünschenswert, wenn es Entropie einführt. (Es sei denn, Sie meinen, es ist weniger präzise und führt somit zu wesentlich weniger potenziellen Samen.)
Nat
15
Ich würde nur vorschlagen, std::random_deviceanstelle von current_time Ihren Zufallsgenerator zu verwenden. Bitte überprüfen Sie alle Referenzbeispiele zu Random.
Aleksander Fular
5
Wenn Sie nicht möchten, dass jemand Ihren Samen errät (und daher Ihre Sequenz reproduziert), ist weniger Präzision nicht gleichbedeutend mit mehr Zufälligkeit. Gehen wir bis zum Äußersten: Runden Sie Ihren Samen auf den nächsten Tag (oder das nächste Jahr?) -> das Erraten ist einfach. Verwenden Sie Femtosekunden-Präzision -> Viel zu raten ...
Linac
2
@ChemicalEngineer Die Granularität von ctimebeträgt 1 Sekunde. Die Granularität der std::chronoImplementierungen ist benutzerdefiniert std::high_resolution_clockund standardmäßig für (in Visual Studio ist es ein Typedef für std::steady_clock) Nanosekunden, kann jedoch eine viel kleinere Messung wählen, daher viel präziser.
Casey
2
@linac Wenn Sie kryptografische Eigenschaften wünschen, verwenden Sie das entsprechende Prng (in dieser Antwort nicht verwendet). Und natürlich kommt auch zeitbasiertes Saatgut nicht in Frage, unabhängig von der versprochenen Präzision.
Cthulhu
-2

Unter Linux ist die Zufallsfunktion keine Zufallsfunktion im probabilistischen Sinne, sondern ein Pseudozufallszahlengenerator. Es wird mit einem Samen gesalzen, und basierend auf diesem Samen sind die produzierten Zahlen pseudozufällig und gleichmäßig verteilt. Der Linux-Weg hat den Vorteil, dass bei der Gestaltung bestimmter Experimente unter Verwendung von Informationen aus Populationen die Wiederholung des Experiments mit bekannter Optimierung der Eingabeinformationen gemessen werden kann. Wenn das endgültige Programm für Tests im realen Leben bereit ist, kann das Salz (Seed) erstellt werden, indem der Benutzer aufgefordert wird, die Maus zu bewegen, die Mausbewegung mit einigen Tastenanschlägen zu mischen und seit Beginn von einen Strich Mikrosekundenzahlen hinzuzufügen das letzte Einschalten.

Windows-Zufallszahlen-Seed wird aus der Sammlung von Maus-, Tastatur-, Netzwerk- und Tageszeitnummern erhalten. Es ist nicht wiederholbar. Dieser Salzwert kann jedoch auf einen bekannten Samen zurückgesetzt werden, wenn, wie oben erwähnt, einer an der Gestaltung eines Experiments beteiligt ist.

Oh ja, Linux hat zwei Zufallszahlengeneratoren. Zum einen ist der Standardwert Modulo 32 Bit und zum anderen Modulo 64 Bit. Ihre Wahl hängt von den Genauigkeitsanforderungen und der Rechenzeit ab, die Sie für Ihre Tests oder die tatsächliche Verwendung benötigen.

Leslie Satenstein
quelle
5
Ich bin mir nicht sicher, warum Sie über den Algorithmus zur Samengenerierung sprechen. OP verwendet eindeutig die Systemzeit als Startwert. Können Sie auch einige Verweise aufcollection of mouse, keyboard, network and time of day numbers
Standardgebietsschema