Che cosa significano 181783497276652981 e 8682522807148012 in Random (Java 7)?


112

Perché sono stati scelti 181783497276652981e 8682522807148012scelti Random.java?

Ecco il codice sorgente pertinente da 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);

Quindi, invocare new Random()senza alcun parametro seed prende l'attuale "seed uniquifier" e lo XOR con System.nanoTime(). Quindi utilizza 181783497276652981per creare un altro uniquificatore seme da memorizzare per la prossima volta che new Random()viene chiamato.

I letterali 181783497276652981Le 8682522807148012Lnon sono inseriti in costanti, ma non appaiono da nessun'altra parte.

All'inizio il commento mi dà un facile vantaggio. La ricerca in linea di quell'articolo produce l'articolo vero e proprio . 8682522807148012non appare nel foglio, ma 181783497276652981appare come sottostringa di un altro numero 1181783497276652981, che è 181783497276652981con a1 preposto.

L'articolo afferma che 1181783497276652981è un numero che fornisce un buon "merito" per un generatore congruente lineare. Questo numero è stato semplicemente copiato erroneamente in Java? Ha 181783497276652981un merito accettabile?

E perché è stato 8682522807148012scelto?

La ricerca in linea di uno dei numeri non fornisce alcuna spiegazione, solo questa pagina che nota anche la caduta 1di fronte 181783497276652981.

Si sarebbero potuti scegliere altri numeri che avrebbero funzionato come questi due numeri? Perché o perché no?


Vorrei solo sottolineare che nessuna delle costanti menzionate (anche quelle più grandi con quelle all'inizio) è troppo grande per adattarsi anche se la moltiplicazione risulterà sicuramente in un overflow.
nanofarad

6
8682522807148012è un'eredità della versione precedente della classe, come si può vedere nelle revisioni effettuate nel 2010 . L' 181783497276652981Lsembra essere un errore di battitura davvero e si potrebbe presentare un bug report.
assylias

6
O è un errore di battitura, cioè un bug, o una caratteristica con motivazioni non rivelate. Dovresti chiedere agli autori. Tutto ciò che otterrai qui sarà solo un'opinione più o meno disinformata. Se pensi che sia un bug, invia una segnalazione di bug.
Marchese di Lorne

1
Soprattutto date le diverse risposte, queste potrebbero essere due domande separate per ciascuna costante.
Mark Hurd

1
Triste vedere un collo di bottiglia della scalabilità globale incorporato in una classe così fondamentale. seedUniquifierpuò diventare estremamente conteso su una scatola a 64 core. Un thread-local sarebbe stato più scalabile.
usr

Risposte:


57
  1. Questo numero è stato semplicemente copiato erroneamente in Java?

    Sì, sembra essere un errore di battitura.

  2. 181783497276652981 ha un merito accettabile?

    Questo potrebbe essere determinato utilizzando l'algoritmo di valutazione presentato nel documento. Ma il merito del numero "originale" è probabilmente più alto.

  3. E perché è stato scelto 8682522807148012?

    Sembra essere casuale. Potrebbe essere il risultato di System.nanoTime () quando il codice è stato scritto.

  4. Si sarebbero potuti scegliere altri numeri che avrebbero funzionato come questi due numeri?

    Non tutti i numeri sarebbero ugualmente "buoni". Quindi no.

Strategie di semina

Esistono differenze nello schema di seeding predefinito tra le diverse versioni e l'implementazione di JRE.

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

Il primo non è accettabile se si creano più RNG di seguito. Se i loro tempi di creazione cadono nello stesso intervallo di millisecondi, daranno sequenze completamente identiche. (stesso seme => stessa sequenza)

Il secondo non è thread-safe. Più thread possono ottenere RNG identici durante l'inizializzazione allo stesso tempo. Inoltre, i semi delle successive inizializzazioni tendono a essere correlati. A seconda dell'effettiva risoluzione del timer del sistema, la sequenza seme potrebbe aumentare in modo lineare (n, n + 1, n + 2, ...). Come affermato in Quanto devono essere diversi i semi casuali? e il documento di riferimento Difetti comuni nell'inizializzazione di generatori di numeri pseudocasuali , i semi correlati possono generare correlazioni tra le sequenze effettive di più RNG.

Il terzo approccio crea seed distribuiti in modo casuale e quindi non correlati, anche tra thread e inizializzazioni successive. Quindi gli attuali documenti java:

Questo costruttore imposta il seme del generatore di numeri casuali su un valore molto probabilmente distinto da qualsiasi altra chiamata di questo costruttore.

potrebbe essere esteso con "tra thread" e "non correlato"

Qualità della sequenza di semi

Ma la casualità della sequenza di semina è buona quanto l'RNG sottostante. L'RNG utilizzato per la sequenza seed in questa implementazione Java utilizza un generatore congruenziale lineare moltiplicativo (MLCG) con c = 0 em = 2 ^ 64. (Il modulo 2 ^ 64 è dato implicitamente dall'overflow di interi lunghi a 64 bit) A causa dello zero c e della potenza del modulo 2, la "qualità" (lunghezza del ciclo, correlazione dei bit, ...) è limitata . Come dice l'articolo, oltre alla lunghezza complessiva del ciclo, ogni singolo bit ha una propria lunghezza del ciclo, che diminuisce in modo esponenziale per i bit meno significativi. Pertanto, i bit inferiori hanno uno schema di ripetizione più piccolo. (Il risultato di seedUniquifier () dovrebbe essere invertito di bit, prima di essere troncato a 48 bit nell'RNG effettivo)

Ma è veloce! E per evitare inutili cicli di confronto e impostazione, il corpo del ciclo dovrebbe essere veloce. Questo probabilmente spiega l'uso di questo specifico MLCG, senza addizioni, senza xoring, solo una moltiplicazione.

E il documento citato presenta un elenco di buoni "moltiplicatori" per c = 0 em = 2 ^ 64, come 1181783497276652981.

Tutto sommato: A per sforzo @ JRE-developer;) Ma c'è un errore di battitura. (Ma chissà, a meno che qualcuno non lo valuti, c'è la possibilità che il primo mancante 1 migliori effettivamente il seeding RNG.)

Ma alcuni moltiplicatori sono decisamente peggiori: "1" porta a una sequenza costante. "2" porta a una sequenza in movimento a bit singolo (in qualche modo correlata) ...

La correlazione inter-sequenza per RNG è effettivamente rilevante per le simulazioni (Monte Carlo), in cui vengono istanziate sequenze casuali multiple e persino parallelizzate. Quindi una buona strategia di seeding è necessaria per ottenere simulazioni "indipendenti". Pertanto lo standard C ++ 11 introduce il concetto di una sequenza di semi per la generazione di semi non correlati.


3
Almeno è ancora strano, se avessero lasciato cadere quella meno significativa invece di quella più significativa allora ogni moltiplicazione perde un po 'fino a quando alla fine (dopo 62 passaggi) la seedUniquifiersi blocca a zero.
Harold

9

Se consideri che l'equazione utilizzata per il generatore di numeri casuali è:

LCGEquation

Dove X (n + 1) è il numero successivo, a è il multiplo, X (n) è il numero corrente, c è l'incremento ed m è il modulo.

Se guardi oltre Random, a, c ed m sono definiti nell'intestazione della classe

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'

e guardando il metodo in protected int next(int bits)cui è implementata l'equazione

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

Ciò implica che il metodo seedUniquifier()sta effettivamente ottenendo X (n) o nel primo caso all'inizializzazione X (0) che in realtà è 8682522807148012 * 181783497276652981, questo valore viene quindi modificato ulteriormente dal valore di System.nanoTime(). Questo algoritmo è coerente con l'equazione precedente ma con la seguente X (0) = 8682522807148012, a = 181783497276652981, m = 2 ^ 64 ec = 0. Ma poiché il mod m di è preformato dal lungo trabocco, l'equazione sopra diventa semplicemente

EQ2

Guardando il foglio , il valore di a = 1181783497276652981è per m = 2 ^ 64, c = 0. Quindi sembra essere solo un errore di battitura e il valore 8682522807148012per X (0) che sembra essere un numero scelto casualmente dal codice legacy per Random. Come visto qui. Ma il merito di questi numeri scelti potrebbe essere ancora valido, ma come menzionato da Thomas B. probabilmente non "buono" come quello del giornale.

EDIT - Di seguito i pensieri originali sono stati chiariti da allora, quindi possono essere ignorati ma lasciandoli come riferimento

Questo mi porta alle conclusioni:

  1. Il riferimento alla carta non è per il valore in sé ma per i metodi utilizzati per ottenere i valori dovuti ai diversi valori di a, ce m

  2. È una semplice coincidenza che il valore sia altrimenti lo stesso diverso dall'1 iniziale e il commento è fuori luogo (ancora fatica a crederci)

O

C'è stato un grave malinteso delle tabelle nel giornale e gli sviluppatori hanno appena scelto un valore a caso poiché nel momento in cui viene moltiplicato qual è stato il punto nell'usare il valore della tabella in primo luogo, soprattutto perché puoi semplicemente fornire il tuo proprio valore seme in ogni modo, nel qual caso questi valori non vengono nemmeno presi in considerazione

Quindi per rispondere alla tua domanda

Si sarebbero potuti scegliere altri numeri che avrebbero funzionato come questi due numeri? Perché o perché no?

Sì, avrebbe potuto essere utilizzato qualsiasi numero, infatti se specifichi un valore di inizializzazione quando installi Random stai utilizzando qualsiasi altro valore. Questo valore non ha alcun effetto sulle prestazioni del generatore, questo è determinato dai valori di a, ce m che sono hard coded all'interno della classe.


1
Non proprio - Ci sono due algoritmi: (i) 1 per creare un nuovo seme casuale ogni volta che viene chiamato il costruttore. Quell'algoritmo usa un semplice X_n + 1 = X_n * a. A causa del lungo overflow, questo è equivalente a X_n + 1 = X_n * a mod m. Con a = 181783497276652981 em = 2 ^ 64. (ii) Un altro algoritmo che, partendo da un dato seme, produce una serie di numeri casuali. Il secondo algoritmo è quello di cui parli e i documenti spiegano che " Questo è un generatore di numeri pseudocasuali congruenti lineari, come descritto da Knuth in The Art of Computer Programming ".
assylias

1
@assylias Capisco il tuo punto, sono rimasto così preso dal codice sorgente Randome dal documento citato che ho completamente superato la domanda originale, la modificherò presto, grazie.
Java Devil

3

Secondo il link che hai fornito, hanno scelto ( dopo aver aggiunto l'1 mancante :) ) il miglior rendimento da 2 ^ 64 perché long non può avere un numero da 2 ^ 128

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.