J'ai créé une classe appelée QuickRandom
, et son travail est de produire rapidement des nombres aléatoires. C'est vraiment simple: il suffit de prendre l'ancienne valeur, de multiplier par a double
et de prendre la partie décimale.
Voici ma QuickRandom
classe dans son intégralité:
public class QuickRandom {
private double prevNum;
private double magicNumber;
public QuickRandom(double seed1, double seed2) {
if (seed1 >= 1 || seed1 < 0) throw new IllegalArgumentException("Seed 1 must be >= 0 and < 1, not " + seed1);
prevNum = seed1;
if (seed2 <= 1 || seed2 > 10) throw new IllegalArgumentException("Seed 2 must be > 1 and <= 10, not " + seed2);
magicNumber = seed2;
}
public QuickRandom() {
this(Math.random(), Math.random() * 10);
}
public double random() {
return prevNum = (prevNum*magicNumber)%1;
}
}
Et voici le code que j'ai écrit pour le tester:
public static void main(String[] args) {
QuickRandom qr = new QuickRandom();
/*for (int i = 0; i < 20; i ++) {
System.out.println(qr.random());
}*/
//Warm up
for (int i = 0; i < 10000000; i ++) {
Math.random();
qr.random();
System.nanoTime();
}
long oldTime;
oldTime = System.nanoTime();
for (int i = 0; i < 100000000; i ++) {
Math.random();
}
System.out.println(System.nanoTime() - oldTime);
oldTime = System.nanoTime();
for (int i = 0; i < 100000000; i ++) {
qr.random();
}
System.out.println(System.nanoTime() - oldTime);
}
C'est un algorithme très simple qui multiplie simplement le double précédent par un double "nombre magique". Je l'ai jeté ensemble assez rapidement, donc je pourrais probablement l'améliorer, mais étrangement, cela semble fonctionner correctement.
Voici un exemple de sortie des lignes commentées dans la main
méthode:
0.612201846732229
0.5823974655091941
0.31062451498865684
0.8324473610354004
0.5907187526770246
0.38650264675748947
0.5243464344127049
0.7812828761272188
0.12417247811074805
0.1322738256858378
0.20614642573072284
0.8797579436677381
0.022122999476108518
0.2017298328387873
0.8394849894162446
0.6548917685640614
0.971667953190428
0.8602096647696964
0.8438709031160894
0.694884972852229
Hm. Assez aléatoire. En fait, cela fonctionnerait pour un générateur de nombres aléatoires dans un jeu.
Voici un exemple de sortie de la partie non commentée:
5456313909
1427223941
Hou la la! Il fonctionne presque 4 fois plus vite queMath.random
.
Je me souviens avoir lu quelque part qui Math.random
utilisait System.nanoTime()
et des tonnes de modules et de divisions folles. Est-ce vraiment nécessaire? Mon algorithme fonctionne beaucoup plus rapidement et cela semble assez aléatoire.
J'ai deux questions:
- Mon algorithme est-il "assez bon" (pour, par exemple, un jeu, où les nombres vraiment aléatoires ne sont pas trop importants)?
- Pourquoi en
Math.random
faire autant alors qu'il semble qu'une simple multiplication et découper la décimale suffiront?
la source
new QuickRandom(0,5)
ounew QuickRandom(.5, 2)
. Ceux-ci afficheront tous les deux à plusieurs reprises 0 pour votre numéro.Réponses:
Votre
QuickRandom
implémentation n'a pas vraiment une distribution uniforme. Les fréquences sont généralement plus élevées aux valeurs inférieures tout enMath.random()
ayant une distribution plus uniforme. Voici un SSCCE qui montre que:Le résultat moyen ressemble à ceci:
Si vous répétez le test, vous verrez que la distribution QR varie fortement, en fonction des graines initiales, tandis que la distribution MR est stable. Parfois, il atteint la distribution uniforme souhaitée, mais le plus souvent, ce n'est pas le cas. Voici l'un des exemples les plus extrêmes, il dépasse même les limites du graphique:
la source
QuickRandom
. Parfois, c'est presque l'uniforme, parfois c'est bien pire que ça.Ce que vous décrivez est un type de générateur aléatoire appelé générateur congruentiel linéaire . Le générateur fonctionne comme suit:
Ce générateur a de nombreuses propriétés intéressantes, mais présente des problèmes importants en tant que bonne source aléatoire. L'article de Wikipedia lié ci-dessus décrit certaines des forces et des faiblesses. En bref, si vous avez besoin de bonnes valeurs aléatoires, ce n'est probablement pas une très bonne approche.
J'espère que cela t'aides!
la source
Votre fonction de nombre aléatoire est médiocre, car elle a trop peu d'état interne - le nombre généré par la fonction à une étape donnée dépend entièrement du nombre précédent. Par exemple, si nous supposons que
magicNumber
c'est 2 (à titre d'exemple), alors la séquence:est fortement reflété par des séquences similaires:
Dans de nombreux cas, cela générera des corrélations notables dans votre jeu - par exemple, si vous effectuez des appels successifs à votre fonction pour générer les coordonnées X et Y des objets, les objets formeront des motifs diagonaux clairs.
Sauf si vous avez de bonnes raisons de croire que le générateur de nombres aléatoires ralentit votre application (et c'est TRÈS improbable), il n'y a aucune bonne raison d'essayer d'écrire le vôtre.
la source
Le vrai problème avec ceci est que son histogramme de sortie dépend beaucoup trop de la graine initiale - la plupart du temps, il se terminera avec une sortie presque uniforme, mais la plupart du temps aura une sortie nettement non uniforme.
Inspiré par cet article sur la mauvaise
rand()
fonction de php , j'ai créé des images matricielles aléatoires en utilisantQuickRandom
etSystem.Random
. Cette course montre comment parfois la graine peut avoir un mauvais effet (dans ce cas en favorisant les nombres inférieurs) alors queSystem.Random
c'est assez uniforme.QuickRandom
System.Random
Encore pire
Si nous initialisons au
QuickRandom
furnew QuickRandom(0.01, 1.03)
et à mesure que nous obtenons cette image:Le code
la source
magicNumber
multiplication produit un nombre similaire àprevNum
, ce qui montre le manque d'aléatoire. Si nous utilisons les graines,new QuickRandom(0.01, 1.03)
nous obtenons ce i.imgur.com/Q1Yunbe.png !System.Drawing.Bitmap
.Un problème avec votre générateur de nombres aléatoires est qu'il n'y a pas `` d'état caché '' - si je sais quel numéro aléatoire vous avez renvoyé lors du dernier appel, je connais chaque numéro aléatoire que vous enverrez jusqu'à la fin des temps, car il n'y en a qu'un résultat suivant possible, et ainsi de suite.
Une autre chose à considérer est la «période» de votre générateur de nombres aléatoires. Evidemment avec une taille d'état finie, égale à la partie mantisse d'un double, il ne pourra renvoyer au plus que 2 ^ 52 valeurs avant bouclage. Mais c'est dans le meilleur des cas - pouvez-vous prouver qu'il n'y a pas de boucles de période 1, 2, 3, 4 ...? S'il y en a, votre RNG aura un comportement horrible et dégénéré dans ces cas.
De plus, votre génération de nombres aléatoires aura-t-elle une distribution uniforme pour tous les points de départ? Si ce n'est pas le cas, votre RNG sera biaisé - ou pire, biaisé de différentes manières en fonction de la graine de départ.
Si vous pouvez répondre à toutes ces questions, c'est génial. Si vous ne pouvez pas, alors vous savez pourquoi la plupart des gens ne réinventent pas la roue et n'utilisent pas un générateur de nombres aléatoires éprouvé;)
(Au fait, un bon adage est le suivant: le code le plus rapide est celui qui ne s'exécute pas. Vous pouvez créer l'aléa () le plus rapide au monde, mais ce n'est pas bon s'il n'est pas très aléatoire)
la source
0 -> 0
. Selon la graine, il peut y en avoir beaucoup d'autres. (Par exemple, avec une graine de 3,0,0.5 -> 0.5
,0.25 -> 0.75 -> 0.25
,0.2 -> 0.6 -> 0.8 -> 0.4 -> 0.2
, etc.)Un test courant que j'ai toujours fait lors du développement de PRNG était de:
Cela m'a permis d'itérer rapidement des idées qui étaient des PRNG "assez bons" pour des séquences d'environ 1 à 20 mégaoctets. Cela a également donné une meilleure image descendante que de simplement l'inspecter à l'œil nu, car tout PRNG "assez bon" avec un demi-mot d'état pourrait rapidement dépasser la capacité de vos yeux à voir le point du cycle.
Si j'étais vraiment pointilleux, je pourrais prendre les bons algorithmes et exécuter les tests DIEHARD / NIST sur eux, pour obtenir plus d'informations, puis revenir en arrière et en modifier davantage.
L'avantage du test de compression, par rapport à une analyse de fréquence est que, trivialement, il est facile de construire une bonne distribution: il suffit de sortir un bloc de 256 longueurs contenant tous les caractères de valeurs 0 à 255, et de le faire 100 000 fois. Mais cette séquence a un cycle de longueur 256.
Une distribution asymétrique, même avec une petite marge, devrait être détectée par un algorithme de compression, en particulier si vous lui donnez suffisamment (disons 1 mégaoctet) de la séquence pour travailler avec. Si certains caractères, ou bigrammes ou n-grammes se produisent plus fréquemment, un algorithme de compression peut coder cette distribution asymétrique en codes qui favorisent les occurrences fréquentes avec des mots de code plus courts, et vous obtenez un delta de compression.
Étant donné que la plupart des algorithmes de compression sont rapides et ne nécessitent aucune implémentation (car les systèmes d'exploitation les ont juste traîner), le test de compression est très utile pour évaluer rapidement réussite / échec pour un PRNG que vous pourriez développer.
Bonne chance avec vos expériences!
Oh, j'ai effectué ce test sur le rng que vous avez ci-dessus, en utilisant le petit mod suivant de votre code:
Les résultats ont été:
Je considérerais un PRNG bon si le fichier de sortie ne pouvait pas être compressé du tout. Pour être honnête, je ne pensais pas que votre PRNG ferait si bien, seulement 16% sur ~ 20 Megs est assez impressionnant pour une construction aussi simple. Mais je considère toujours que c'est un échec.
la source
Le générateur aléatoire le plus rapide que vous puissiez implémenter est le suivant:
XD, blagues à part, en plus de tout ce qui est dit ici, j'aimerais contribuer en citant que tester des séquences aléatoires "est une tâche difficile" [1], et qu'il existe plusieurs tests qui vérifient certaines propriétés des nombres pseudo-aléatoires, vous pouvez trouver un beaucoup d'entre eux ici: http://www.random.org/analysis/#2005
Un moyen simple d'évaluer la «qualité» du générateur aléatoire est l'ancien test du Chi Square.
Citant [1]
En utilisant cette théorie et le code suivant:
J'ai obtenu le résultat suivant:
Qui, pour QuickRandom, est loin de r (en dehors de
r ± 2 * sqrt(r)
)Cela dit, QuickRandom pourrait être rapide mais (comme indiqué dans une autre réponse) n'est pas bon comme générateur de nombres aléatoires
[1] SEDGEWICK ROBERT, Algorithms in C , Addinson Wesley Publishing Company, 1990, pages 516 à 518
la source
int[]
sont initialisés à zéro, donc pas besoin de cette partie. Lancer pour flotter est inutile lorsque vous travaillez avec des doubles. Dernier: appeler les noms de méthodes random1 et random2 est assez drôle.J'ai mis en place une maquette rapide de votre algorithme en JavaScript pour évaluer les résultats. Il génère 100 000 entiers aléatoires de 0 à 99 et suit l'instance de chaque entier.
La première chose que je remarque, c'est que vous êtes plus susceptible d'obtenir un nombre faible qu'un nombre élevé. Vous voyez cela le plus quand
seed1
c'est haut etseed2
bas. Dans quelques cas, je n'ai obtenu que 3 numéros.Au mieux, votre algorithme a besoin d'être affiné.
la source
Si la
Math.Random()
fonction appelle le système d'exploitation pour obtenir l'heure de la journée, vous ne pouvez pas la comparer à votre fonction. Votre fonction est un PRNG, alors que cette fonction recherche des nombres aléatoires réels. Pommes et oranges.Votre PRNG peut être rapide, mais il n'a pas suffisamment d'informations d'état pour atteindre une longue période avant de se répéter (et sa logique n'est pas assez sophistiquée pour même atteindre les périodes qui sont possibles avec autant d'informations d'état).
La période est la durée de la séquence avant que votre PRNG ne commence à se répéter. Cela se produit dès que la machine PRNG effectue une transition d'état vers un état qui est identique à un état passé. À partir de là, il répétera les transitions qui ont commencé dans cet état. Un autre problème avec les PRNG peut être un faible nombre de séquences uniques, ainsi qu'une convergence dégénérée sur une séquence particulière qui se répète. Il peut également y avoir des modèles indésirables. Par exemple, supposons qu'un PRNG semble assez aléatoire lorsque les nombres sont imprimés en décimal, mais une inspection des valeurs en binaire montre que le bit 4 bascule simplement entre 0 et 1 à chaque appel. Oups!
Jetez un œil au Mersenne Twister et à d'autres algorithmes. Il existe des moyens de trouver un équilibre entre la durée de la période et les cycles du processeur. Une approche de base (utilisée dans le Mersenne Twister) consiste à faire un cycle dans le vecteur d'état. C'est-à-dire que lorsqu'un nombre est généré, il n'est pas basé sur l'état entier, juste sur quelques mots du tableau d'états soumis à quelques opérations sur quelques bits. Mais à chaque étape, l'algorithme se déplace également dans le tableau, brouillant le contenu petit à petit.
la source
/dev/random
c'est une source d'aléa réel obtenue à partir des pilotes de périphériques, et non d'un PRNG. Il se bloque lorsqu'il n'y a pas assez de bits disponibles. Le périphérique sœur/dev/urandom
ne se bloque pas non plus, mais ce n'est toujours pas exactement un PRNG car il est mis à jour avec des bits aléatoires lorsqu'ils sont disponibles.nanoTime()
+ counter / hash est utilisé pour la graine par défautjava.util.Random
d'oracle / OpenJDK. C'est pour la graine seulement alors c'est un LCG standard. En effet, le générateur OP prend 2 nombres aléatoires pour la graine, ce qui est ok - donc pas de différence quejava.util.Random
.System.currentTimeMillis()
était la tête de série par défaut dans JDK1.4-Il existe de très nombreux générateurs de nombres pseudo aléatoires. Par exemple, le ranarray de Knuth , le twister de Mersenne , ou recherchez des générateurs LFSR. Les monumental "algorithmes seminumériques" de Knuth analysent la zone et proposent des générateurs linéaires congruentiels (simples à mettre en œuvre, rapides).
Mais je vous suggère de vous en tenir à
java.util.Random
ouMath.random
, ils sont rapides et au moins OK pour une utilisation occasionnelle (c'est-à-dire des jeux et autres). Si vous êtes juste paranoïaque sur la distribution (un programme de Monte Carlo ou un algorithme génétique), vérifiez leur implémentation (la source est disponible quelque part) et semez-les avec un nombre vraiment aléatoire, soit à partir de votre système d'exploitation, soit à partir de random.org . Si cela est nécessaire pour certaines applications où la sécurité est essentielle, vous devrez creuser vous-même. Et comme dans ce cas vous ne devriez pas croire ce qu'un carré coloré avec des morceaux manquants jaillit ici, je vais me taire maintenant.la source
Il est très peu probable que les performances de génération de nombres aléatoires soient un problème pour tout cas d'utilisation que vous avez proposé à moins d'accéder à une seule
Random
instance à partir de plusieurs threads (parce queRandom
c'est le cassynchronized
).Cependant, si tel est vraiment le cas et que vous avez besoin de beaucoup de nombres aléatoires rapidement, votre solution est beaucoup trop peu fiable. Parfois cela donne de bons résultats, parfois cela donne des résultats horribles (basés sur les paramètres initiaux).
Si vous voulez les mêmes nombres que la
Random
classe vous donne, mais plus rapidement, vous pouvez vous débarrasser de la synchronisation là-dedans:J'ai simplement pris le
java.util.Random
code et supprimé la synchronisation qui se traduit par deux fois plus de performances que l'original sur mon Oracle HotSpot JVM 7u9. Il est toujours plus lent que le vôtreQuickRandom
, mais il donne des résultats beaucoup plus cohérents. Pour être précis, pour les mêmesseed
valeurs et les applications à thread unique, il donne les mêmes nombres pseudo-aléatoires que laRandom
classe d' origine .Ce code est basé sur le courant
java.util.Random
d'OpenJDK 7u qui est sous licence GNU GPL v2 .MODIFIER 10 mois plus tard:
Je viens de découvrir que vous n'avez même pas besoin d'utiliser mon code ci-dessus pour obtenir une
Random
instance non synchronisée . Il y en a aussi un dans le JDK!Regardez la
ThreadLocalRandom
classe de Java 7 . Le code à l'intérieur est presque identique à mon code ci-dessus. La classe est simplement uneRandom
version isolée des threads locaux, adaptée pour générer rapidement des nombres aléatoires. Le seul inconvénient auquel je peux penser est que vous ne pouvez pas le définirseed
manuellement.Exemple d'utilisation:
la source
:)
C'est intéressant, merci!`` Aléatoire '' ne consiste pas seulement à obtenir des nombres ... ce que vous avez est pseudo-aléatoire
Si le pseudo-aléatoire est assez bon pour vos besoins, alors bien sûr, c'est beaucoup plus rapide (et XOR + Bitshift sera plus rapide que ce que vous avez)
Rolf
Éditer:
OK, après avoir été trop rapide dans cette réponse, permettez-moi de répondre à la vraie raison pour laquelle votre code est plus rapide:
À partir de JavaDoc for Math.Random ()
C'est probablement pourquoi votre code est plus rapide.
la source
Math.random()
est plus lente. Il faudrait soit synchroniser ou créer un nouveau àRandom
chaque fois, et ni l'un ni l'autre n'est très attrayant en termes de performances. Si je tenais à la performance, je créerais la miennenew Random
et je l'utiliserais simplement. : Pjava.util.Random n'est pas très différent, un LCG basique décrit par Knuth. Cependant, il présente 2 principaux avantages / différences:
Ci-dessous se trouve la routine principale générant des entiers «aléatoires» dans java.util.Random.
Si vous supprimez AtomicLong et l'état non divulgué (c'est-à-dire en utilisant tous les bits du
long
), vous obtiendrez plus de performances que la double multiplication / modulo.Dernière note:
Math.random
ne doit être utilisé que pour des tests simples, il est sujet à des conflits et si vous avez même quelques threads qui l'appellent simultanément, les performances se dégradent. Une caractéristique historique peu connue de celui-ci est l'introduction de CAS en Java - pour battre un benchmark tristement célèbre (d'abord par IBM via intrinsèques, puis Sun a fait "CAS from Java")la source
C'est la fonction aléatoire que j'utilise pour mes jeux. C'est assez rapide et a une bonne (assez) distribution.
la source
volatile
, le compilateur est libre d'éliminer (ou d'introduire) des variables locales à volonté.