Pourquoi ont été 181783497276652981
et 8682522807148012
choisis Random.java
?
Voici le code source pertinent 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);
Ainsi, l'invocation new Random()
sans aucun paramètre de départ prend le "seed uniquifier" actuel et le XOR avec System.nanoTime()
. Ensuite, il utilise 181783497276652981
pour créer un autre uniquificateur de départ à stocker pour la prochaine fois qu'il new Random()
est appelé.
Les littéraux 181783497276652981L
et 8682522807148012L
ne sont pas placés dans des constantes, mais ils n'apparaissent nulle part ailleurs.
Au début, le commentaire me donne une piste facile. La recherche en ligne de cet article produit l'article réel . 8682522807148012
n'apparaît pas dans le papier, mais 181783497276652981
apparaît - comme sous-chaîne d'un autre nombre 1181783497276652981
, qui est précédé d' 181783497276652981
un 1
.
Le papier prétend que 1181783497276652981
c'est un nombre qui donne un bon «mérite» pour un générateur congruentiel linéaire. Ce numéro a-t-il été simplement mal copié dans Java? At 181783497276652981
-il un mérite acceptable?
Et pourquoi a-t-il été 8682522807148012
choisi?
La recherche en ligne de l'un ou l'autre des nombres ne donne aucune explication, seulement cette page qui remarque également la chute 1
devant 181783497276652981
.
Aurait-on pu choisir d'autres nombres qui auraient fonctionné aussi bien que ces deux nombres? Pourquoi ou pourquoi pas?
8682522807148012
est un héritage de la version précédente de la classe, comme on peut le voir dans les révisions effectuées en 2010 . Cela181783497276652981L
semble être une faute de frappe et vous pouvez déposer un rapport de bogue.seedUniquifier
peut devenir extrêmement contesté sur une boîte de 64 noyaux. Un thread local aurait été plus évolutif.Réponses:
Oui, semble être une faute de frappe.
Cela pourrait être déterminé à l'aide de l'algorithme d'évaluation présenté dans l'article. Mais le mérite du nombre «original» est probablement plus élevé.
Semble être aléatoire. Cela pourrait être le résultat de System.nanoTime () lorsque le code a été écrit.
Tous les nombres ne seraient pas également «bons». Donc non.
Stratégies d'ensemencement
Il existe des différences dans le schéma d'amorçage par défaut entre les différentes versions et l'implémentation du JRE.
Le premier n'est pas acceptable si vous créez plusieurs RNG à la suite. Si leurs temps de création tombent dans la même plage de millisecondes, ils donneront des séquences complètement identiques. (même graine => même séquence)
Le second n'est pas thread-safe. Plusieurs threads peuvent obtenir des RNG identiques lors de l'initialisation en même temps. De plus, les germes des initialisations ultérieures ont tendance à être corrélés. En fonction de la résolution de la minuterie réelle du système, la séquence d'amorçage pourrait être linéairement croissante (n, n + 1, n + 2, ...). Comme indiqué dans Dans quelle mesure les graines aléatoires doivent-elles être différentes? et l'article référencé Défauts communs dans l'initialisation des générateurs de nombres pseudo-aléatoires , les germes corrélés peuvent générer une corrélation entre les séquences réelles de plusieurs RNG.
La troisième approche crée des germes distribués de manière aléatoire et donc non corrélés, même entre les threads et les initialisations ultérieures. Donc, la documentation java actuelle:
pourrait être étendu par "à travers les threads" et "non corrélé"
Qualité de la séquence de semences
Mais le caractère aléatoire de la séquence d'ensemencement n'est aussi bon que le RNG sous-jacent. Le RNG utilisé pour la séquence de départ dans cette implémentation java utilise un générateur congruentiel linéaire multiplicatif (MLCG) avec c = 0 et m = 2 ^ 64. (Le module 2 ^ 64 est implicitement donné par le débordement d'entiers longs de 64 bits) En raison du zéro c et du module de puissance de 2, la "qualité" (longueur de cycle, corrélation de bits, ...) est limitée . Comme le dit l'article, en plus de la longueur totale du cycle, chaque bit a sa propre longueur de cycle, qui diminue de manière exponentielle pour les bits moins significatifs. Ainsi, les bits inférieurs ont un motif de répétition plus petit. (Le résultat de seedUniquifier () doit être inversé en bits, avant d'être tronqué à 48 bits dans le RNG réel)
Mais c'est rapide! Et pour éviter les boucles de comparaison et de définition inutiles, le corps de la boucle doit être rapide. Ceci explique probablement l'utilisation de ce MLCG spécifique, sans ajout, sans xoring, juste une multiplication.
Et l'article mentionné présente une liste de bons "multiplicateurs" pour c = 0 et m = 2 ^ 64, comme 1181783497276652981.
Dans l'ensemble: A pour l'effort @ JRE-développeurs;) Mais il y a une faute de frappe. (Mais qui sait, à moins que quelqu'un ne l'évalue, il est possible que le premier 1 manquant améliore réellement le RNG de départ.)
Mais certains multiplicateurs sont certainement pires: "1" conduit à une séquence constante. "2" conduit à une séquence de déplacement d'un seul bit (en quelque sorte corrélée) ...
La corrélation inter-séquence pour les RNG est en fait pertinente pour les simulations (Monte Carlo), où plusieurs séquences aléatoires sont instanciées et même parallélisées. Une bonne stratégie de semis est donc nécessaire pour obtenir des simulations «indépendantes». Par conséquent, la norme C ++ 11 introduit le concept de séquence de semences pour générer des semences non corrélées.
la source
seedUniquifier
devienne bloqué à zéro.Si vous considérez que l'équation utilisée pour le générateur de nombres aléatoires est:
Où X (n + 1) est le nombre suivant, a est le multiplicateur, X (n) est le nombre courant, c est l'incrément et m est le module.
Si vous regardez plus loin
Random
, a, c et m sont définis dans l'en-tête de la classeet en regardant la méthode où
protected int next(int bits)
l'équation est implémentéeCela implique que la méthode
seedUniquifier()
obtient effectivement X (n) ou dans le premier cas à l'initialisation X (0) qui est en fait8682522807148012 * 181783497276652981
, cette valeur est ensuite modifiée davantage par la valeur deSystem.nanoTime()
. Cet algorithme est cohérent avec l'équation ci-dessus mais avec le X (0) =8682522807148012
, a =181783497276652981
, m = 2 ^ 64 et c = 0. Mais comme le mod m de est préformé par le long débordement, l'équation ci-dessus devientEn regardant le papier , la valeur de a =
1181783497276652981
est pour m = 2 ^ 64, c = 0. Il semble donc être juste une faute de frappe et la valeur8682522807148012
de X (0) qui semble être un nombre apparemment choisi au hasard dans le code hérité pourRandom
. Comme vu ici. Mais le mérite de ces nombres choisis pourrait encore être valable mais comme mentionné par Thomas B. probablement pas aussi «bon» que celui dans le papier.EDIT - Les pensées originales ci-dessous ont depuis été clarifiées et peuvent donc être ignorées, mais en les laissant pour référence
Cela m'amène aux conclusions:
La référence à l'article n'est pas pour la valeur elle-même mais pour les méthodes utilisées pour obtenir les valeurs dues aux différentes valeurs de a, c et m
Ce n'est qu'une simple coïncidence si la valeur est par ailleurs la même autre que le premier 1 et le commentaire est mal placé (on a encore du mal à le croire)
OU
Il y a eu un grave malentendu sur les tableaux dans le document et les développeurs viennent de choisir une valeur au hasard car au moment où elle est multipliée, quel était l'intérêt d'utiliser la valeur de la table en premier lieu, d'autant plus que vous pouvez simplement fournir votre propre valeur de départ, auquel cas ces valeurs ne sont même pas prises en compte
Donc, pour répondre à votre question
Oui, n'importe quel nombre aurait pu être utilisé, en fait si vous spécifiez une valeur de départ lors de l'instanciation aléatoire, vous utilisez une autre valeur. Cette valeur n'a aucun effet sur les performances du générateur, cela est déterminé par les valeurs de a, c et m qui sont codées en dur dans la classe.
la source
Random
et l'article cité que j'ai complètement dépassé la question originale, sera bientôt édité, merci.Selon le lien que vous avez fourni, ils ont choisi ( après avoir ajouté le 1 manquant :) ) le meilleur rendement de 2 ^ 64 car longtemps ne peut pas avoir un nombre de 2 ^ 128
la source