Was ist mit 181783497276652981 und 8682522807148012 in Random (Java 7)?

112

Warum wurden 181783497276652981und 8682522807148012ausgewählt Random.java?

Hier ist der relevante Quellcode aus Java SE JDK 1.7:

/**
 * Creates a new random number generator. This constructor sets
 * the seed of the random number generator to a value very likely
 * to be distinct from any other invocation of this constructor.
 */
public Random() {
    this(seedUniquifier() ^ System.nanoTime());
}

private static long seedUniquifier() {
    // L'Ecuyer, "Tables of Linear Congruential Generators of
    // Different Sizes and Good Lattice Structure", 1999
    for (;;) {
        long current = seedUniquifier.get();
        long next = current * 181783497276652981L;
        if (seedUniquifier.compareAndSet(current, next))
            return next;
    }
}

private static final AtomicLong seedUniquifier
    = new AtomicLong(8682522807148012L);

new Random()Wenn Sie also ohne einen Seed-Parameter aufrufen, wird der aktuelle "Seed Uniquifier" verwendet und mit XORs versehen System.nanoTime(). Anschließend wird 181783497276652981ein weiterer Seed-Uniquifier erstellt, der beim nächsten Aufruf gespeichert werden new Random()soll.

Die Literale 181783497276652981Lund 8682522807148012Lwerden nicht in Konstanten platziert, aber sie erscheinen nirgendwo anders.

Der Kommentar gibt mir zunächst einen einfachen Hinweis. Wenn Sie online nach diesem Artikel suchen, erhalten Sie den tatsächlichen Artikel . 8682522807148012erscheint nicht im Papier, sondern 181783497276652981erscheint - als Teilzeichenfolge einer anderen Zahl 1181783497276652981, die 181783497276652981mit einem 1vorangestellten Zeichen versehen ist.

Das Papier behauptet, dass dies 1181783497276652981eine Zahl ist, die einen guten "Verdienst" für einen linearen Kongruenzgenerator ergibt. Wurde diese Nummer einfach falsch in Java kopiert? Hat 181783497276652981ein akzeptables Verdienst?

Und warum wurde 8682522807148012gewählt?

Die Online-Suche nach einer der beiden Nummern liefert keine Erklärung, nur diese Seite , auf der auch das vorangestellte Feld 1angezeigt wird 181783497276652981.

Könnten andere Nummern gewählt worden sein, die genauso gut funktioniert hätten wie diese beiden Nummern? Warum oder warum nicht?

rgettman
quelle
Ich möchte nur darauf hinweisen, dass keine der genannten Konstanten (auch die größeren mit den am Anfang) zu groß sind, um zu passen, obwohl die Multiplikation sicherlich zu einem Überlauf führen wird.
Nanofarad
6
8682522807148012ist ein Erbe der vorherigen Version der Klasse, wie aus den 2010 vorgenommenen Überarbeitungen hervorgeht . Das 181783497276652981Lscheint in der Tat ein Tippfehler zu sein und Sie könnten einen Fehlerbericht einreichen.
Assylias
6
Entweder ist es ein Tippfehler, dh ein Fehler, oder eine Funktion mit nicht genannter Motivation. Sie müssten die Autoren fragen. Alles, was Sie hier bekommen, ist nur eine mehr oder weniger uninformierte Meinung. Wenn Sie glauben, dass es sich um einen Fehler handelt, senden Sie einen Fehlerbericht.
Marquis von Lorne
1
Insbesondere angesichts der unterschiedlichen Antworten können dies zwei separate Fragen für jede Konstante sein.
Mark Hurd
1
Es ist traurig zu sehen, dass ein globaler Engpass bei der Skalierbarkeit in eine so grundlegende Klasse eingebaut ist. seedUniquifierkann auf einer 64-Core-Box extrem umstritten sein. Ein Thread-Local wäre skalierbarer gewesen.
usr

Antworten:

57
  1. Wurde diese Nummer einfach falsch in Java kopiert?

    Ja, scheint ein Tippfehler zu sein.

  2. Hat 181783497276652981 einen akzeptablen Wert?

    Dies könnte unter Verwendung des in der Arbeit vorgestellten Bewertungsalgorithmus bestimmt werden. Aber der Wert der "ursprünglichen" Zahl ist wahrscheinlich höher.

  3. Und warum wurde 8682522807148012 ausgewählt?

    Scheint zufällig zu sein. Dies könnte das Ergebnis von System.nanoTime () sein, als der Code geschrieben wurde.

  4. Könnten andere Nummern gewählt worden sein, die genauso gut funktioniert hätten wie diese beiden Nummern?

    Nicht jede Zahl wäre gleich "gut". Also nein.

Seeding-Strategien

Es gibt Unterschiede im Standard-Seeding-Schema zwischen verschiedenen Versionen und der Implementierung der JRE.

public Random() { this(System.currentTimeMillis()); }
public Random() { this(++seedUniquifier + System.nanoTime()); }
public Random() { this(seedUniquifier() ^ System.nanoTime()); }

Der erste ist nicht akzeptabel, wenn Sie mehrere RNGs hintereinander erstellen. Wenn ihre Erstellungszeiten im gleichen Millisekundenbereich liegen, ergeben sie völlig identische Sequenzen. (gleicher Samen => gleiche Sequenz)

Der zweite ist nicht threadsicher. Mehrere Threads können bei gleichzeitiger Initialisierung identische RNGs erhalten. Zusätzlich neigen Samen nachfolgender Initialisierungen dazu, korreliert zu sein. Abhängig von der tatsächlichen Timer-Auflösung des Systems kann die Startsequenz linear ansteigen (n, n + 1, n + 2, ...). Wie in Wie unterschiedlich müssen zufällige Samen sein? und das referenzierte Papier Häufige Fehler bei der Initialisierung von Pseudozufallszahlengeneratoren , korrelierte Seeds können eine Korrelation zwischen den tatsächlichen Sequenzen mehrerer RNGs erzeugen.

Der dritte Ansatz erzeugt zufällig verteilte und damit nicht korrelierte Seeds, selbst über Threads und nachfolgende Initialisierungen hinweg. Also die aktuellen Java-Dokumente:

Dieser Konstruktor setzt den Startwert des Zufallszahlengenerators auf einen Wert, der sich sehr wahrscheinlich von jedem anderen Aufruf dieses Konstruktors unterscheidet.

könnte durch "über Threads" und "unkorreliert" erweitert werden

Samensequenzqualität

Die Zufälligkeit der Seeding-Sequenz ist jedoch nur so gut wie das zugrunde liegende RNG. Das in dieser Java-Implementierung für die Seed-Sequenz verwendete RNG verwendet einen multiplikativen linearen Kongruenzgenerator (MLCG) mit c = 0 und m = 2 ^ 64. (Der Modul 2 ^ 64 ist implizit durch den Überlauf von 64 Bit langen ganzen Zahlen gegeben.) Aufgrund der Null c und der Potenz des 2-Moduls ist die "Qualität" (Zykluslänge, Bitkorrelation, ...) begrenzt . Wie das Papier sagt, hat neben der Gesamtzykluslänge jedes einzelne Bit eine eigene Zykluslänge, die für weniger signifikante Bits exponentiell abnimmt. Somit haben niedrigere Bits ein kleineres Wiederholungsmuster. (Das Ergebnis von seedUniquifier () sollte bitumgekehrt werden, bevor es im tatsächlichen RNG auf 48 Bit abgeschnitten wird.)

Aber es ist schnell! Und um unnötige Compare-and-Set-Loops zu vermeiden, sollte der Loop-Body schnell sein. Dies erklärt wahrscheinlich die Verwendung dieses spezifischen MLCG, ohne Addition, ohne Xoring, nur eine Multiplikation.

Und das erwähnte Papier enthält eine Liste guter "Multiplikatoren" für c = 0 und m = 2 ^ 64 als 1181783497276652981.

Alles in allem: A für Mühe @ JRE-Entwickler;) Aber es gibt einen Tippfehler. (Aber wer weiß, es sei denn, jemand bewertet es, es besteht die Möglichkeit, dass die fehlende führende 1 tatsächlich das Seeding-RNG verbessert.)

Einige Multiplikatoren sind jedoch definitiv schlechter: "1" führt zu einer konstanten Folge. "2" führt zu einer Einzelbit-Bewegungssequenz (irgendwie korreliert) ...

Die Intersequenzkorrelation für RNGs ist tatsächlich relevant für (Monte-Carlo-) Simulationen, bei denen mehrere Zufallssequenzen instanziiert und sogar parallelisiert werden. Daher ist eine gute Seeding-Strategie erforderlich, um "unabhängige" Simulationsläufe zu erhalten. Daher führt der C ++ 11-Standard das Konzept einer Seed-Sequenz zur Erzeugung nicht korrelierter Seeds ein.

Thomas B.
quelle
3
Zumindest ist es immer noch seltsam, wenn sie die niedrigstwertige anstelle der höchstwertigen fallen gelassen haben, dann verliert jede Multiplikation ein wenig, bis sie schließlich (nach 62 Schritten) seedUniquifierbei Null stecken bleibt.
Harold
9

Wenn Sie bedenken, dass die für den Zufallszahlengenerator verwendete Gleichung lautet:

LCGE-Gleichung

Wobei X (n + 1) die nächste Zahl ist, a der Multiplikator ist, X (n) die aktuelle Zahl ist, c das Inkrement ist und m der Modul ist.

Wenn Sie genauer hinschauen Random, werden a, c und m in der Kopfzeile der Klasse definiert

private static final long multiplier = 0x5DEECE66DL;   //= 25214903917 -- 'a'
private static final long addend = 0xBL;               //= 11          -- 'c'
private static final long mask = (1L << 48) - 1;       //= 2 ^ 48 - 1  -- 'm'

und wenn man sich die Methode ansieht, bei der protected int next(int bits)die Gleichung implementiert wird

nextseed = (oldseed * multiplier + addend) & mask;
//X(n+1) =  (X(n)   *      a     +    c  ) mod m

Dies impliziert, dass das Verfahren seedUniquifier()tatsächlich X (n) erhält oder im ersten Fall bei der Initialisierung X (0), was tatsächlich ist 8682522807148012 * 181783497276652981, dass dieser Wert dann durch den Wert von weiter modifiziert wird System.nanoTime(). Dieser Algorithmus stimmt mit der obigen Gleichung überein, jedoch mit dem folgenden X (0) = 8682522807148012, a = 181783497276652981, m = 2 ^ 64 und c = 0. Da jedoch der Mod m von durch den langen Überlauf vorgeformt wird, wird die obige Gleichung gerade

Gl. 2

Auf dem Papier ist der Wert von a = 1181783497276652981für m = 2 ^ 64, c = 0. Es scheint also nur ein Tippfehler zu sein, und der Wert 8682522807148012für X (0) scheint eine scheinbar zufällig ausgewählte Zahl aus dem Legacy-Code zu sein für Random. Wie hier zu sehen. Aber das Verdienst dieser gewählten Zahlen könnte immer noch gültig sein, aber wie von Thomas B. erwähnt, wahrscheinlich nicht so "gut" wie das in der Zeitung.

BEARBEITEN - Die folgenden ursprünglichen Gedanken wurden inzwischen geklärt, können also ignoriert werden, lassen sie jedoch als Referenz

Dies führt mich zu den Schlussfolgerungen:

  1. Der Verweis auf das Papier bezieht sich nicht auf den Wert selbst, sondern auf die Methoden, mit denen die Werte aufgrund der unterschiedlichen Werte von a, c und m erhalten werden

  2. Es ist nur ein Zufall, dass der Wert ansonsten der gleiche ist wie der führende 1 und der Kommentar falsch platziert ist (obwohl er immer noch Schwierigkeiten hat, dies zu glauben).

ODER

Es gab ein ernstes Missverständnis der Tabellen in diesem Artikel, und die Entwickler haben gerade einen zufälligen Wert ausgewählt, da zu dem Zeitpunkt, an dem er multipliziert wird, der Sinn der Verwendung des Tabellenwerts an erster Stelle lag, insbesondere, weil Sie nur Ihren Wert angeben können eigener Startwert in irgendeiner Weise. In diesem Fall werden diese Werte nicht einmal berücksichtigt

Also, um deine Frage zu beantworten

Könnten andere Nummern gewählt worden sein, die genauso gut funktioniert hätten wie diese beiden Nummern? Warum oder warum nicht?

Ja, es könnte eine beliebige Zahl verwendet worden sein. Wenn Sie beim Instantiate Random einen Startwert angeben, verwenden Sie einen anderen Wert. Dieser Wert hat keinen Einfluss auf die Leistung des Generators. Er wird durch die Werte von a, c und m bestimmt, die innerhalb der Klasse fest codiert sind.

Java Devil
quelle
1
Nicht wirklich - Es gibt zwei Algorithmen: (i) 1, um bei jedem Aufruf des Konstruktors einen neuen zufälligen Startwert zu erstellen. Dieser Algo verwendet ein einfaches X_n + 1 = X_n * a. Aufgrund des langen Überlaufs entspricht dies X_n + 1 = X_n * a mod m. Mit a = 181783497276652981 und m = 2 ^ 64. (ii) Ein anderes Algo, das ausgehend von einem gegebenen Samen eine Reihe von Zufallszahlen erzeugt. Dieses zweite Algo ist das, das Sie erwähnen, und die Dokumente erklären, dass " dies ein linearer kongruenter Pseudozufallszahlengenerator ist, wie von Knuth in The Art of Computer Programming beschrieben ".
Assylias
1
@assylias Ich verstehe Ihren Standpunkt, wurde so in den Quellcode von Randomund das zitierte Papier verwickelt, dass ich die ursprüngliche Frage völlig überschritten habe, wird bald bearbeitet, danke.
Java Devil
3

Gemäß dem von Ihnen angegebenen Link haben sie ( nach dem Hinzufügen der fehlenden 1 :) die beste Ausbeute von 2 ^ 64 ausgewählt, da long keine Zahl von 2 ^ 128 haben kann

Jaffar Ramay
quelle