Warum wiederholt rand() Zahlen viel öfter unter Linux als auf Mac?

Lesezeit: 10 Minuten

Benutzeravatar von Theron S
Theron S

Ich habe im Rahmen eines Projekts, an dem ich arbeite, eine Hashmap in C implementiert und zufällige Einfügungen verwendet, um sie zu testen. ich habe bemerkt, dass rand() Unter Linux scheinen sich Zahlen viel öfter zu wiederholen als unter Mac. RAND_MAX ist 2147483647/0x7FFFFFFF auf beiden Plattformen. Ich habe es auf dieses Testprogramm reduziert, das ein Byte-Array erstellt RAND_MAX+1-lang, erzeugt RAND_MAX Zufallszahlen, stellt fest, ob es sich jeweils um ein Duplikat handelt, und streicht es wie gesehen von der Liste.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main() {
    size_t size = ((size_t)RAND_MAX) + 1;
    char *randoms = calloc(size, sizeof(char));
    int dups = 0;
    srand(time(0));
    for (int i = 0; i < RAND_MAX; i++) {
        int r = rand();
        if (randoms[r]) {
            // printf("duplicate at %d\n", r);
            dups++;
        }
        randoms[r] = 1;
    }
    printf("duplicates: %d\n", dups);
}

Linux generiert ständig rund 790 Millionen Duplikate. Der Mac generiert konsequent nur eine, also durchläuft er jede Zufallszahl, die er generieren kann fast ohne zu wiederholen. Kann mir bitte jemand erklären, wie das funktioniert? Ich kann nichts anderes sagen als die man Seiten, kann nicht sagen, welches RNG jeweils verwendet wird, und kann online nichts finden. Vielen Dank!

  • Da rand() Werte von 0 bis einschließlich RAND_MAX zurückgibt, muss Ihr Array die Größe RAND_MAX+1 haben

    – Hochofen

    24. April 2020 um 15:19 Uhr

  • Sie haben vielleicht bemerkt, dass RAND_MAX/e ~= 790 Millionen ist. Auch die Grenze von (1-1/n)^n, wenn n gegen unendlich geht, ist 1/e.

    – David Schwartz

    24. April 2020 um 15:28 Uhr


  • @DavidSchwartz Wenn ich dich richtig verstehe, könnte das erklären, warum die Zahl unter Linux konstant bei 790 Millionen liegt. Ich denke, die Frage ist dann: warum/wie funktioniert Mac nicht so oft wiederholen?

    – Theron S

    24. April 2020 um 15:33 Uhr

  • Es gibt keine Qualitätsanforderungen für das PRNG in der Laufzeitbibliothek. Die einzige wirkliche Anforderung ist die Wiederholbarkeit mit demselben Seed. Anscheinend ist die Qualität des PRNG in Ihrem Linux besser als in Ihrem Mac.

    – pmg

    24. April 2020 um 15:35 Uhr


  • @chux Ja, aber da es auf Multiplikation basiert, kann der Zustand niemals Null sein oder das Ergebnis (nächster Zustand) wäre auch Null. Basierend auf dem Quellcode prüft es als Sonderfall auf Null, wenn es mit Null gesät ist, aber es erzeugt niemals Null als Teil der Sequenz.

    – Arkku

    24. April 2020 um 16:16 Uhr


Benutzeravatar von Arkku
Arkku

Während es zunächst wie das macOS klingen mag rand() ist irgendwie besser, keine Zahlen zu wiederholen, man sollte beachten, dass dies bei dieser Menge an generierten Zahlen der Fall ist erwartet viele Duplikate zu sehen (tatsächlich rund 790 Millionen oder (231-1)/e). Ebenso würde das Durchlaufen der Zahlen nacheinander keine Duplikate erzeugen, aber nicht als sehr zufällig angesehen werden. Also das Linux rand() Umsetzung ist bei dieser Prüfung nicht von einer echten Zufallsquelle zu unterscheiden, während das macOS rand() ist nicht.

Auf den ersten Blick überraschend erscheint auch, wie das macOS rand() kann es so gut schaffen, Duplikate zu vermeiden. Anschauen seinen Quellcodefinden wir die Implementierung wie folgt:

/*
 * Compute x = (7^5 * x) mod (2^31 - 1)
 * without overflowing 31 bits:
 *      (2^31 - 1) = 127773 * (7^5) + 2836
 * From "Random number generators: good ones are hard to find",
 * Park and Miller, Communications of the ACM, vol. 31, no. 10,
 * October 1988, p. 1195.
 */
    long hi, lo, x;

    /* Can't be initialized with 0, so use another value. */
    if (*ctx == 0)
        *ctx = 123459876;
    hi = *ctx / 127773;
    lo = *ctx % 127773;
    x = 16807 * lo - 2836 * hi;
    if (x < 0)
        x += 0x7fffffff;
    return ((*ctx = x) % ((unsigned long) RAND_MAX + 1));

Dies ergibt tatsächlich alle Zahlen zwischen 1 und RAND_MAX, einschließlich, genau einmal, bevor sich die Sequenz erneut wiederholt. Da der nächste Zustand auf Multiplikation basiert, kann der Zustand niemals Null sein (sonst wären alle zukünftigen Zustände ebenfalls Null). Daher ist die wiederholte Zahl, die Sie sehen, die erste, und Null ist diejenige, die nie zurückgegeben wird.

Apple fördert die Verwendung besserer Zufallszahlengeneratoren in seiner Dokumentation und seinen Beispielen mindestens so lange, wie es macOS (oder OS X) gibt, daher die Qualität von rand() wird wahrscheinlich nicht als wichtig erachtet, und sie haben sich einfach an einen der einfachsten verfügbaren Pseudozufallsgeneratoren gehalten. (Wie Sie bemerkt haben, ihre rand() ist sogar mit einer Anwendungsempfehlung kommentiert arc4random() stattdessen.)

In diesem Zusammenhang ist der einfachste Pseudozufallszahlengenerator, den ich finden konnte und der bei diesem (und vielen anderen) Tests auf Zufälligkeit anständige Ergebnisse liefert, xorshift*:

uint64_t x = *ctx;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
*ctx = x;
return (x * 0x2545F4914F6CDD1DUL) >> 33;

Diese Implementierung führt in Ihrem Test zu ziemlich genau 790 Millionen Duplikaten.

  • EIN Zeitschriftenartikel veröffentlicht in den 1980er Jahren einen statistischen Test für PRNGs auf der Grundlage des “Geburtstagsproblems”.

    – Schlafanzug

    24. April 2020 um 17:37 Uhr

  • “Apple hat in seiner Dokumentation die Verwendung besserer Zufallszahlengeneratoren gefördert” –> natürlich könnte Apple damit arbeiten arc4random() wie Code dahinter rand() und ein gutes bekommen rand() Ergebnis. Anstatt zu versuchen, Programmierer dazu zu bringen, anders zu codieren, erstellen Sie einfach bessere Bibliotheksfunktionen. “sie sind einfach stecken geblieben” ist ihre Wahl.

    – chux – Wiedereinsetzung von Monica

    24. April 2020 um 18:16 Uhr


  • das Fehlen eines konstanten Offsets in Macs rand() macht es so schlimm, dass es für die praktische Verwendung nicht sinnvoll ist: Warum gibt rand() % 7 immer 0 zurück?, Rand() % 14 erzeugt nur die Werte 6 oder 13

    – phuklv

    25. April 2020 um 5:47 Uhr


  • @PeterCordes: Es gibt eine solche Anforderung an rand, dass eine erneute Ausführung mit demselben Startwert dieselbe Sequenz erzeugt. OpenBSDs rand gebrochen ist und sich nicht an diesen Vertrag hält.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    25. April 2020 um 5:55 Uhr

  • @R..GitHubSTOPHELPINGICE Sehen Sie eine C-Anforderung, die rand() mit demselben Seed dieselbe Sequenz zwischen verschiedenen Versionen der Bibliothek erzeugen? Eine solche Garantie könnte für Regressionstests zwischen Bibliotheksversionen nützlich sein, aber ich finde keine C-Anforderung dafür.

    – chux – Wiedereinsetzung von Monica

    25. April 2020 um 7:54 Uhr

Benutzeravatar von r3mainer
r3mainer

MacOS bietet eine undokumentierte rand()-Funktion in stdlib. Wenn Sie es nicht gesetzt lassen, sind die ersten ausgegebenen Werte 16807, 282475249, 1622650073, 984943658 und 1144108930. A schnelle Suche wird zeigen, dass diese Sequenz einem sehr einfachen LCG-Zufallszahlengenerator entspricht, der die folgende Formel iteriert:

xn+1 = 75 · xn (Mod 231 − 1)

Da der Zustand dieses RNG vollständig durch den Wert einer einzelnen 32-Bit-Ganzzahl beschrieben wird, ist seine Periode nicht sehr lang. Genauer gesagt wiederholt es sich alle 231 − 2 Iterationen, wobei jeder Wert von 1 bis 2 ausgegeben wird31 − 2.

Ich glaube nicht, dass es eine gibt Standard Implementierung von rand() für alle Versionen von Linux, aber es gibt eine glibc rand()-Funktion das wird oft verwendet. Anstelle einer einzelnen 32-Bit-Zustandsvariablen wird hier ein Pool von über 1000 Bits verwendet, was praktisch nie eine sich vollständig wiederholende Sequenz erzeugen wird. Auch hier können Sie wahrscheinlich herausfinden, welche Version Sie haben, indem Sie die ersten Ausgaben dieses RNG drucken, ohne es zuerst zu impfen. (Die Funktion glibc rand() erzeugt die Zahlen 1804289383, 846930886, 1681692777, 1714636915 und 1957747793.)

Der Grund, warum Sie unter Linux mehr Kollisionen bekommen (und kaum welche unter MacOS), ist, dass die Linux-Version von rand() im Grunde zufälliger ist.

  • ein ungesät rand() muss sich wie einer mit verhalten srand(1);

    – pmg

    24. April 2020 um 16:02 Uhr

  • Der Quellcode für die rand() in macOS ist verfügbar: opensource.apple.com/source/Libc/Libc-1353.11.2/stdlib/FreeBSD/… FWIW, ich habe den gleichen Test gegen diesen aus der Quelle kompilierten durchgeführt und er führt tatsächlich nur zu einem Duplikat. Apple fördert die Verwendung anderer Zufallszahlengeneratoren (wie z arc4random() bevor Swift übernahm) in ihren Beispielen und Dokumentationen, also die Verwendung von rand() ist in nativen Apps auf ihren Plattformen wahrscheinlich nicht sehr verbreitet, was erklären könnte, warum es nicht besser ist.

    – Arkku

    24. April 2020 um 16:09 Uhr


  • Danke für die Antwort, das beantwortet meine Frage. Und eine Periode von (2^31)-2 erklärt, warum es sich gleich am Ende zu wiederholen beginnt, wie ich beobachtet habe. Sie (@r3mainer) sagten rand() war nicht dokumentiert, aber @Arkku hat einen Link zur offensichtlichen Quelle bereitgestellt. Weiß einer von Ihnen, warum ich diese Datei auf meinem System nicht finden kann und warum ich nur sehe int rand(void) __swift_unavailable("Use arc4random instead."); bei Macs stdlib.h? Ich nehme an, der Code, mit dem @Arkku verknüpft ist, wurde nur in … welche Bibliothek kompiliert?

    – Theron S

    24. April 2020 um 16:27 Uhr


  • @TheronS Es wird in die C-Bibliothek libc kompiliert, /usr/lib/libc.dylib. =)

    – Arkku

    24. April 2020 um 16:28 Uhr

  • Welche Version von rand() ein bestimmtes C-Programm verwendet, wird nicht durch den „Compiler“ oder das „Betriebssystem“ bestimmt, sondern durch die Implementierung der C-Standardbibliothek (z. glibc, libc.dylib, msvcrt*.dll).

    – Peter O.

    24. April 2020 um 17:05 Uhr

cmaster - Benutzeravatar von Monica wiederherstellen
cmaster – monica wieder einsetzen

rand() wird durch den C-Standard definiert, und der C-Standard gibt nicht an, welcher Algorithmus zu verwenden ist. Offensichtlich verwendet Apple einen schlechteren Algorithmus als Ihre GNU/Linux-Implementierung: Der Linux-Algorithmus ist in Ihrem Test nicht von einer echten Zufallsquelle zu unterscheiden, während die Apple-Implementierung nur die Zahlen mischt.

Wenn Sie Zufallszahlen beliebiger Qualität wünschen, verwenden Sie entweder einen besseren PRNG, der zumindest einige Garantien für die Qualität der zurückgegebenen Zahlen gibt, oder lesen Sie einfach aus /dev/urandom oder ähnliches. Letzteres gibt Ihnen Zahlen in kryptografischer Qualität, ist aber langsam. Auch wenn es alleine zu langsam ist, /dev/urandom kann einige ausgezeichnete Samen für ein anderes, schnelleres PRNG liefern.

  • Danke für die Antwort. Ich brauche eigentlich kein gutes PRNG, war nur besorgt, dass in meiner Hashmap ein undefiniertes Verhalten lauert, und wurde dann neugierig, als ich diese Möglichkeit ausschloss und die Plattformen sich immer noch anders verhielten.

    – Theron S

    24. April 2020 um 16:36 Uhr

  • Übrigens hier ist ein Beispiel für einen kryptografisch sicheren Zufallszahlengenerator: github.com/divinity76/phpcpp/commit/… – aber es ist C++ statt C und ich lasse die STL-Implementierer das ganze schwere Heben machen.

    – Hansenrik

    25. April 2020 um 10:17 Uhr

  • @hanshenrik Ein Krypto-RNG ist im Allgemeinen übertrieben und zu langsam für eine einfache Hash-Tabelle.

    – PM 2Ring

    25. April 2020 um 20:32 Uhr

  • @PM2Ring Absolut. Ein Hash-Tabellen-Hash muss in erster Linie schnell sein, nicht gut. Wenn Sie jedoch einen Hash-Tabellen-Algorithmus entwickeln möchten, der nicht nur schnell, sondern auch anständig ist, ist es meiner Meinung nach von Vorteil, einige der Tricks kryptografischer Hash-Algorithmen zu kennen. Es wird Ihnen helfen, die meisten der auffälligsten Fehler zu vermeiden, die die meisten schnellen Hash-Algorithmen heimsuchen. Trotzdem hätte ich hier nicht für eine konkrete Umsetzung geworben.

    – Cmaster – Wiedereinsetzung von Monica

    25. April 2020 um 22:47 Uhr

  • @cmaster Wahr genug. Es ist sicherlich eine gute Idee, ein bisschen über Dinge wie zu wissen Mischfunktionen und die Lawineneffekt. Glücklicherweise gibt es Nicht-Krypto-Hash-Funktionen mit guten Eigenschaften, die (bei richtiger Implementierung) nicht zu viel Geschwindigkeit opfern, z. B. xxhash, murmur3 oder siphash.

    – PM 2Ring

    25. April 2020 um 23:03 Uhr


Im Allgemeinen wurde das rand/srand-Paar lange Zeit als veraltet angesehen, da niederwertige Bits in den Ergebnissen weniger Zufälligkeit aufweisen als höherwertige Bits. Dies kann etwas mit Ihren Ergebnissen zu tun haben oder auch nicht, aber ich denke, dies ist immer noch eine gute Gelegenheit, sich daran zu erinnern, dass, obwohl einige rand/srand-Implementierungen jetzt aktueller sind, ältere Implementierungen bestehen bleiben und es besser ist, random(3 ). Auf meiner Arch-Linux-Box ist der folgende Hinweis immer noch in der Manpage für rand(3):

  The versions of rand() and srand() in the Linux C Library use the  same
   random number generator as random(3) and srandom(3), so the lower-order
   bits should be as random as the higher-order bits.  However,  on  older
   rand()  implementations,  and  on  current implementations on different
   systems, the lower-order bits are much less random than the  higher-or-
   der bits.  Do not use this function in applications intended to be por-
   table when good randomness is needed.  (Use random(3) instead.)

Direkt darunter gibt die Manpage tatsächlich sehr kurze, sehr einfache Beispielimplementierungen von rand und srand, die ungefähr die einfachsten LC-RNGs sind, die Sie je gesehen haben, und die einen kleinen RAND_MAX haben. Ich glaube nicht, dass sie mit dem übereinstimmen, was in der C-Standardbibliothek enthalten ist, falls dies jemals der Fall war. Oder zumindest hoffe ich nicht.

Wenn Sie etwas aus der Standardbibliothek verwenden möchten, verwenden Sie im Allgemeinen random, wenn Sie können (die Manpage listet es als POSIX-Standard bis POSIX.1-2001 auf, aber rand ist Standard, lange bevor C überhaupt standardisiert wurde). . Oder noch besser, knacken Sie Numerical Recipes (oder suchen Sie online danach) oder Knuth und implementieren Sie eines. Sie sind wirklich einfach und Sie müssen es nur einmal tun, um einen Allzweck-RNG mit den Attributen zu haben, die Sie am häufigsten benötigen, und der von bekannter Qualität ist.

1422830cookie-checkWarum wiederholt rand() Zahlen viel öfter unter Linux als auf Mac?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy