¿Qué pasa con 181783497276652981 y 8682522807148012 en Random (Java 7)?

112

¿Por qué fueron 181783497276652981y 8682522807148012elegido en Random.java?

Aquí está el código fuente relevante de 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);

Por lo tanto, la invocación new Random()sin ningún parámetro semilla toma el "uniquificador semilla" actual y lo XOR conSystem.nanoTime() . Luego se usa 181783497276652981para crear otro uniquificador de semillas que se almacenará para la próxima vez que new Random()se llame.

Los literales 181783497276652981L y 8682522807148012Lno se colocan en constantes, pero no aparecen en ningún otro lugar.

Al principio, el comentario me da una pista fácil. La búsqueda en línea de ese artículo produce el artículo real . 8682522807148012no aparece en el documento, pero 181783497276652981aparece, como una subcadena de otro número,1181783497276652981 , que tiene 181783497276652981un 1prefijo.

El periódico afirma que 1181783497276652981 es un número que ofrece un buen "mérito" para un generador congruencial lineal. ¿Este número simplemente se copió incorrectamente en Java? ¿ 181783497276652981Tiene un mérito aceptable?

Y por que fue 8682522807148012 elegido?

La búsqueda en línea de cualquiera de los números no proporciona ninguna explicación, solo esta página que también nota el que se ha caído 1en frente de 181783497276652981.

¿Se podrían haber elegido otros números que hubieran funcionado tan bien como estos dos números? ¿Por qué o por qué no?

rgettman
fuente
Solo me gustaría señalar que ninguna de las constantes mencionadas (incluso las más grandes con las que están al principio) son demasiado grandes para caber, aunque la multiplicación seguramente resultará en un desbordamiento.
nanofarad
6
8682522807148012es un legado de la versión anterior de la clase, como se puede ver en las revisiones realizadas en 2010 . De hecho, 181783497276652981Lparece ser un error tipográfico y podría presentar un informe de error.
assylias
6
O es un error tipográfico, es decir, un error, o una característica con una motivación no revelada. Tendrías que preguntar a los autores. Todo lo que obtenga aquí será solo una opinión más o menos desinformada. Si cree que es un error, envíe un informe de error.
Marqués de Lorne
1
Especialmente dadas las diferentes respuestas, esto podría ser dos preguntas separadas para cada constante.
Mark Hurd
1
Es triste ver un cuello de botella de escalabilidad global integrado en una clase tan fundamental. seedUniquifierpuede ser extremadamente competitivo en una caja de 64 núcleos. Un subproceso local habría sido más escalable.
usr

Respuestas:

57
  1. ¿Este número simplemente se copió incorrectamente en Java?

    Sí, parece ser un error tipográfico.

  2. ¿Tiene 181783497276652981 un mérito aceptable?

    Esto podría determinarse utilizando el algoritmo de evaluación presentado en el documento. Pero el mérito del número "original" probablemente sea mayor.

  3. ¿Y por qué se eligió 8682522807148012?

    Parece ser aleatorio. Podría ser el resultado de System.nanoTime () cuando se escribió el código.

  4. ¿Se podrían haber elegido otros números que hubieran funcionado tan bien como estos dos números?

    No todos los números serían igualmente "buenos". Entonces, no.

Estrategias de siembra

Existen diferencias en el esquema de inicialización predeterminado entre las diferentes versiones y la implementación del JRE.

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

El primero no es aceptable si crea varios RNG seguidos. Si sus tiempos de creación caen en el mismo rango de milisegundos, darán secuencias completamente idénticas. (misma semilla => misma secuencia)

El segundo no es seguro para subprocesos. Varios subprocesos pueden obtener RNG idénticos cuando se inicializan al mismo tiempo. Además, las semillas de inicializaciones posteriores tienden a estar correlacionadas. Dependiendo de la resolución real del temporizador del sistema, la secuencia de semillas podría aumentar linealmente (n, n + 1, n + 2, ...). Como se indica en ¿Cuán diferentes deben ser las semillas aleatorias? y el documento de referencia. Defectos comunes en la inicialización de generadores de números pseudoaleatorios , las semillas correlacionadas pueden generar correlación entre las secuencias reales de múltiples RNG.

El tercer enfoque crea semillas distribuidas aleatoriamente y, por lo tanto, no correlacionadas, incluso entre subprocesos e inicializaciones posteriores. Entonces, los documentos java actuales:

Este constructor establece la semilla del generador de números aleatorios en un valor muy probablemente distinto de cualquier otra invocación de este constructor.

podría extenderse "a través de subprocesos" y "sin correlación"

Calidad de la secuencia de semillas

Pero la aleatoriedad de la secuencia de siembra es tan buena como el RNG subyacente. El RNG usado para la secuencia semilla en esta implementación de Java usa un generador congruencial lineal multiplicativo (MLCG) con c = 0 ym = 2 ^ 64. (El módulo 2 ^ 64 está implícitamente dado por el desbordamiento de enteros largos de 64 bits) Debido al cero cy el módulo de potencia de 2, la "calidad" (duración del ciclo, correlación de bits, ...) es limitada . Como dice el documento, además de la duración total del ciclo, cada bit tiene una duración de ciclo propia, que disminuye exponencialmente para los bits menos significativos. Por lo tanto, los bits inferiores tienen un patrón de repetición más pequeño. (El resultado de seedUniquifier () debe invertirse en bits, antes de truncarlo a 48 bits en el RNG real)

¡Pero es rápido! Y para evitar bucles de comparación y configuración innecesarios, el cuerpo del bucle debe ser rápido. Esto probablemente explica el uso de este MLCG específico, sin adición, sin xoreo, solo una multiplicación.

Y el artículo mencionado presenta una lista de buenos "multiplicadores" para c = 0 y m = 2 ^ 64, como 1181783497276652981.

Considerándolo todo: A por esfuerzo @ JRE-developers;) Pero hay un error tipográfico. (Pero quién sabe, a menos que alguien lo evalúe, existe la posibilidad de que el 1 inicial faltante en realidad mejore el RNG inicial).

Pero algunos multiplicadores son definitivamente peores: "1" conduce a una secuencia constante. "2" conduce a una secuencia de movimiento de un solo bit (de alguna manera correlacionada) ...

La correlación entre secuencias para los RNG es realmente relevante para las simulaciones (de Monte Carlo), donde se instancian e incluso se paralelizan múltiples secuencias aleatorias. Por lo tanto, es necesaria una buena estrategia de siembra para obtener ejecuciones de simulación "independientes". Por lo tanto, el estándar C ++ 11 introduce el concepto de Seed Sequence para generar semillas no correlacionadas.

Thomas B.
fuente
3
Al menos sigue siendo extraño, si hubieran eliminado el menos significativo en lugar del más significativo, entonces cada multiplicación pierde un poco hasta que finalmente (después de 62 pasos) se seedUniquifierqueda atascado en cero.
harold
9

Si considera que la ecuación utilizada para el generador de números aleatorios es:

LCGEcuación

Donde X (n + 1) es el siguiente número, a es el multiplicador, X (n) es el número actual, c es el incremento ym es el módulo.

Si observa más a fondo Random, a, c y m se definen en el encabezado de la clase

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'

y mirando el método en protected int next(int bits)el que se implementa la ecuación

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

Esto implica que el método seedUniquifier()está obteniendo X (n) o, en el primer caso, en la inicialización X (0), que en realidad es 8682522807148012 * 181783497276652981, este valor se modifica más por el valor de System.nanoTime(). Este algoritmo es consistente con la ecuación anterior pero con la siguiente X (0) = 8682522807148012, a = 181783497276652981, m = 2 ^ 64 yc = 0. Pero como el mod m de está preformado por el desbordamiento largo, la ecuación anterior simplemente se convierte en

eq2

Mirando el documento , el valor de a = 1181783497276652981es para m = 2 ^ 64, c = 0. Entonces parece ser solo un error tipográfico y el valor 8682522807148012de X (0) que parece ser un número aparentemente elegido al azar del código heredado para Random. Como se ve aquí. Pero el mérito de estos números elegidos aún podría ser válido, pero como lo menciona Thomas B. probablemente no sea tan "bueno" como el del artículo.

EDITAR: los pensamientos originales a continuación se han aclarado, por lo que se pueden ignorar, pero dejándolos como referencia

Esto me lleva a las conclusiones:

  1. La referencia al artículo no es por el valor en sí, sino por los métodos utilizados para obtener los valores debido a los diferentes valores de a, cy m

  2. Es mera coincidencia que el valor sea el mismo que el 1 inicial y el comentario esté fuera de lugar (aunque todavía estoy luchando por creer esto)

O

Ha habido un malentendido grave de las tablas en el documento y los desarrolladores acaban de elegir un valor al azar, ya que en el momento en que se multiplica, ¿cuál era el punto de usar el valor de la tabla en primer lugar, especialmente porque solo puede proporcionar su poseer el valor de semilla de cualquier manera, en cuyo caso estos valores ni siquiera se tienen en cuenta

Entonces para responder a tu pregunta

¿Se podrían haber elegido otros números que hubieran funcionado tan bien como estos dos números? ¿Por qué o por qué no?

Sí, se podría haber usado cualquier número, de hecho, si especifica un valor de inicialización cuando crea una instancia aleatoria, está usando cualquier otro valor. Este valor no tiene ningún efecto sobre el rendimiento del generador, esto está determinado por los valores de a, cym que están codificados dentro de la clase.

Diablo de Java
fuente
1
En realidad no: hay dos algoritmos: (i) 1 para crear una nueva semilla aleatoria cada vez que se llama al constructor. Ese algoritmo usa un simple X_n + 1 = X_n * a. Debido al largo desbordamiento, esto equivale a X_n + 1 = X_n * a mod m. Con a = 181783497276652981 y m = 2 ^ 64. (ii) Otro algoritmo, que, a partir de una semilla dada, produce una serie de números aleatorios. Ese segundo algoritmo es el que mencionas y los documentos explican que " Este es un generador de números pseudoaleatorios congruentes lineales, como lo describe Knuth en El arte de la programación informática ".
assylias
1
@assylias Veo su punto, me quedé tan atrapado en el código fuente de Randomy el documento citado que supere por completo la pregunta original, la editaré pronto, gracias.
Java Devil
3

Según el enlace que proporcionó, han elegido ( después de agregar el 1 faltante :) ) el mejor rendimiento de 2 ^ 64 porque no puede tener un número de 2 ^ 128.

Jaffar Ramay
fuente