web-dev-qa-db-fra.com

Est-ce un algorithme aléatoire "assez bon"; pourquoi n'est-il pas utilisé s'il est plus rapide?

J'ai créé une classe appelée QuickRandom, et son travail consiste à produire rapidement des nombres aléatoires. C'est très simple: il suffit de prendre l'ancienne valeur, de la multiplier par un double et de prendre la partie décimale.

Voici ma classe QuickRandom 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);
}

Il s'agit d'un algorithme très simple qui multiplie simplement le double précédent par un double "nombre magique". Je l'ai assemblé assez rapidement, donc je pourrais probablement l'améliorer, mais étrangement, cela semble bien fonctionner.

Voici un exemple de sortie des lignes commentées dans la méthode main:

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 que Math.random.

Je me souviens avoir lu quelque part que Math.random Utilisait System.nanoTime() et des tonnes de modules et de divisions fous. Est-ce vraiment nécessaire? Mon algorithme fonctionne beaucoup plus rapidement et il semble assez aléatoire.

J'ai deux questions:

  • Mon algorithme est-il "assez bon" (pour, disons, un jeu où vraiment les nombres aléatoires ne sont pas trop importants)?
  • Pourquoi Math.random Fait-il autant alors qu'il semble qu'une simple multiplication et que la décimale suffise?
170
Doorknob

Votre implémentation QuickRandom n'a pas vraiment une distribution uniforme. Les fréquences sont généralement plus élevées aux valeurs inférieures tandis que Math.random() a une distribution plus uniforme. Voici un SSCCE qui montre que:

package com.stackoverflow.q14491966;

import Java.util.Arrays;

public class Test {

    public static void main(String[] args) throws Exception {
        QuickRandom qr = new QuickRandom();
        int[] frequencies = new int[10];
        for (int i = 0; i < 100000; i++) {
            frequencies[(int) (qr.random() * 10)]++;
        }
        printDistribution("QR", frequencies);

        frequencies = new int[10];
        for (int i = 0; i < 100000; i++) {
            frequencies[(int) (Math.random() * 10)]++;
        }
        printDistribution("MR", frequencies);
    }

    public static void printDistribution(String name, int[] frequencies) {
        System.out.printf("%n%s distribution |8000     |9000     |10000    |11000    |12000%n", name);
        for (int i = 0; i < 10; i++) {
            char[] bar = "                                                  ".toCharArray(); // 50 chars.
            Arrays.fill(bar, 0, Math.max(0, Math.min(50, frequencies[i] / 100 - 80)), '#');
            System.out.printf("0.%dxxx: %6d  :%s%n", i, frequencies[i], new String(bar));
        }
    }

}

Le résultat moyen ressemble à ceci:

QR distribution |8000     |9000     |10000    |11000    |12000
0.0xxx:  11376  :#################################                 
0.1xxx:  11178  :###############################                   
0.2xxx:  11312  :#################################                 
0.3xxx:  10809  :############################                      
0.4xxx:  10242  :######################                            
0.5xxx:   8860  :########                                          
0.6xxx:   9004  :##########                                        
0.7xxx:   8987  :#########                                         
0.8xxx:   9075  :##########                                        
0.9xxx:   9157  :###########                                       

MR distribution |8000     |9000     |10000    |11000    |12000
0.0xxx:  10097  :####################                              
0.1xxx:   9901  :###################                               
0.2xxx:  10018  :####################                              
0.3xxx:   9956  :###################                               
0.4xxx:   9974  :###################                               
0.5xxx:  10007  :####################                              
0.6xxx:  10136  :#####################                             
0.7xxx:   9937  :###################                               
0.8xxx:  10029  :####################                              
0.9xxx:   9945  :###################    

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 plus souvent qu'il ne le fait pas. Voici l'un des exemples les plus extrêmes, il est même au-delà des frontières du graphique:

QR distribution |8000     |9000     |10000    |11000    |12000
0.0xxx:  41788  :##################################################
0.1xxx:  17495  :##################################################
0.2xxx:  10285  :######################                            
0.3xxx:   7273  :                                                  
0.4xxx:   5643  :                                                  
0.5xxx:   4608  :                                                  
0.6xxx:   3907  :                                                  
0.7xxx:   3350  :                                                  
0.8xxx:   2999  :                                                  
0.9xxx:   2652  :                                                  
351
BalusC

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:

  • Commencez avec une valeur de départ et un multiplicateur.
  • Pour générer un nombre aléatoire:
    • Multipliez la graine par le multiplicateur.
    • Définissez la valeur de départ égale à cette valeur.
    • Renvoie cette valeur.

Ce générateur possède de nombreuses propriétés Nice, mais présente des problèmes importants en tant que bonne source aléatoire. L'article 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!

133
templatetypedef

Votre fonction de nombre aléatoire est médiocre, car elle a trop peu d'état interne - le nombre émis par la fonction à une étape donnée dépend entièrement du nombre précédent. Par exemple, si nous supposons que magicNumber est 2 (à titre d'exemple), alors la séquence:

0.10 -> 0.20

est fortement reflété par des séquences similaires:

0.09 -> 0.18
0.11 -> 0.22

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 des coordonnées X et Y pour les objets, les objets formeront des motifs diagonaux clairs.

À moins que vous n'ayez de bonnes raisons de croire que le générateur de nombres aléatoires ralentit votre application (et c'est TRÈS peu probable), il n'y a aucune bonne raison d'essayer d'écrire la vôtre.

113
duskwuff

Le vrai problème avec cela est que son histogramme de sortie dépend beaucoup de la graine initiale - la plupart du temps, il se retrouvera avec une sortie presque uniforme, mais la plupart du temps aura une sortie nettement non uniforme.

Inspiré par cet article sur la façon dont la fonction Rand() de php est mauvaise , j'ai fait des images matricielles aléatoires en utilisant QuickRandom et System.Random. Cette analyse montre comment parfois la graine peut avoir un mauvais effet (dans ce cas en favorisant des nombres inférieurs) alors que System.Random Est assez uniforme.

QuickRandom

System.Random

Encore pire

Si nous initialisons QuickRandom comme new QuickRandom(0.01, 1.03) nous obtenons cette image:

Le code

using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace QuickRandomTest
{
    public class QuickRandom
    {
        private double prevNum;
        private readonly double magicNumber;

        private static readonly Random Rand = new Random();

        public QuickRandom(double seed1, double seed2)
        {
            if (seed1 >= 1 || seed1 < 0) throw new ArgumentException("Seed 1 must be >= 0 and < 1, not " + seed1);
            prevNum = seed1;
            if (seed2 <= 1 || seed2 > 10) throw new ArgumentException("Seed 2 must be > 1 and <= 10, not " + seed2);
            magicNumber = seed2;
        }

        public QuickRandom()
            : this(Rand.NextDouble(), Rand.NextDouble() * 10)
        {
        }

        public double Random()
        {
            return prevNum = (prevNum * magicNumber) % 1;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var Rand = new Random();
            var qrand = new QuickRandom();
            int w = 600;
            int h = 600;
            CreateMatrix(w, h, Rand.NextDouble).Save("System.Random.png", ImageFormat.Png);
            CreateMatrix(w, h, qrand.Random).Save("QuickRandom.png", ImageFormat.Png);
        }

        private static Image CreateMatrix(int width, int height, Func<double> f)
        {
            var bitmap = new Bitmap(width, height);
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    var c = (int) (f()*255);
                    bitmap.SetPixel(x, y, Color.FromArgb(c,c,c));
                }
            }

            return bitmap;
        }
    }
}
110
Callum Rogers

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 et ainsi de suite.

Une autre chose à considérer est la "période" de votre générateur de nombres aléatoires. Évidemment, avec une taille d'état fini, égale à la portion mantisse d'un double, il ne pourra retourner que 2 ^ 52 valeurs au maximum avant de boucler. 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 le pouvez pas, vous savez pourquoi la plupart des gens ne réinventent pas la roue et utilisent un générateur de nombres aléatoires éprouvé;)

(Soit dit en passant, un bon adage est: le code le plus rapide est un code qui ne s'exécute pas. Vous pouvez créer le plus rapide random () au monde, mais ce n'est pas bon s'il n'est pas très aléatoire)

37
Patashu

Un test commun que j'ai toujours fait lors du développement de PRNG était de:

  1. Convertir la sortie en valeurs char
  2. Écrire la valeur des caractères dans un fichier
  3. Compresser le fichier

Cela m'a permis d'itérer rapidement sur des idées qui étaient des "PRNGs" assez bons "pour des séquences d'environ 1 à 20 mégaoctets. Il a également donné une meilleure image de haut en bas que de simplement l'inspecter à l'œil nu, car tout "assez bon" PRNG avec un demi-mot d'état pourrait rapidement dépasser la capacité de vos yeux à voir le point de 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 modifier encore plus.

L'avantage du test de compression, par opposition à 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 par une petite marge, devrait être captée par un algorithme de compression, en particulier si vous lui donnez suffisamment (disons 1 mégaoctet) de la séquence pour fonctionner. Si certains caractères, bigrammes ou n-grammes se produisent plus fréquemment, un algorithme de compression peut coder ce biais de distribution 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înés), le test de compression est très utile pour évaluer rapidement la réussite/l'échec d'un PRNG vous pourriez être en développement.

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:

import Java.io.*;

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;
    }

    public static void main(String[] args) throws Exception {
        QuickRandom qr = new QuickRandom();
        FileOutputStream fout = new FileOutputStream("qr20M.bin");

        for (int i = 0; i < 20000000; i ++) {
            fout.write((char)(qr.random()*256));
        }
    }
}

Les résultats ont été:

Cris-Mac-Book-2:rt cris$ Zip -9 qr20M.Zip qr20M.bin2
adding: qr20M.bin2 (deflated 16%)
Cris-Mac-Book-2:rt cris$ ls -al
total 104400
drwxr-xr-x   8 cris  staff       272 Jan 25 05:09 .
drwxr-xr-x+ 48 cris  staff      1632 Jan 25 05:04 ..
-rw-r--r--   1 cris  staff      1243 Jan 25 04:54 QuickRandom.class
-rw-r--r--   1 cris  staff       883 Jan 25 05:04 QuickRandom.Java
-rw-r--r--   1 cris  staff  16717260 Jan 25 04:55 qr20M.bin.gz
-rw-r--r--   1 cris  staff  20000000 Jan 25 05:07 qr20M.bin2
-rw-r--r--   1 cris  staff  16717402 Jan 25 05:09 qr20M.Zip

Je considérerais un PRNG bien 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.

36
Cris Stringfellow

Le générateur aléatoire le plus rapide que vous pourriez implémenter est le suivant:

enter image description here

XD, blagues à part, en plus de tout ce qui est dit ici, je voudrais contribuer en citant que tester des séquences aléatoires "est une tâche difficile" [1], et 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é" d'un générateur aléatoire est l'ancien test du Chi Square.

static double chisquare(int numberCount, int maxRandomNumber) {
    long[] f = new long[maxRandomNumber];
    for (long i = 0; i < numberCount; i++) {
        f[randomint(maxRandomNumber)]++;
    }

    long t = 0;
    for (int i = 0; i < maxRandomNumber; i++) {
        t += f[i] * f[i];
    }
    return (((double) maxRandomNumber * t) / numberCount) - (double) (numberCount);
}

Citant [1]

L'idée du test χ² est de vérifier si les nombres produits sont ou non répartis de façon raisonnable. Si nous générons [~ # ~] n [~ # ~] nombres positifs inférieurs à r , alors nous nous attendrions à obtenir environ [~ # ~] n [~ # ~] / r nombres de chaque valeur. Mais --- et c'est l'essence même de la question --- les fréquences d'occurrence de toutes les valeurs ne devraient pas être exactement les mêmes: ce ne serait pas aléatoire!

Nous calculons simplement la somme des carrés des fréquences d'occurrence de chaque valeur, mise à l'échelle par la fréquence attendue, puis soustrayons la taille de la séquence. Ce nombre, la "statistique χ²", peut être exprimé mathématiquement comme

chi squared formula

Si la statistique χ² est proche de r , alors les nombres sont aléatoires; s'il est trop éloigné, ce n'est pas le cas. Les notions de "proche" et de "loin" peuvent être définies plus précisément: il existe des tableaux qui indiquent exactement comment relier la statistique aux propriétés des séquences aléatoires. Pour le test simple que nous effectuons, la statistique doit être comprise entre 2√r

En utilisant cette théorie et le code suivant:

abstract class RandomFunction {
    public abstract int randomint(int range); 
}

public class test {
    static QuickRandom qr = new QuickRandom();

    static double chisquare(int numberCount, int maxRandomNumber, RandomFunction function) {
        long[] f = new long[maxRandomNumber];
        for (long i = 0; i < numberCount; i++) {
            f[function.randomint(maxRandomNumber)]++;
        }

        long t = 0;
        for (int i = 0; i < maxRandomNumber; i++) {
            t += f[i] * f[i];
        }
        return (((double) maxRandomNumber * t) / numberCount) - (double) (numberCount);
    }

    public static void main(String[] args) {
        final int ITERATION_COUNT = 1000;
        final int N = 5000000;
        final int R = 100000;

        double total = 0.0;
        RandomFunction qrRandomInt = new RandomFunction() {
            @Override
            public int randomint(int range) {
                return (int) (qr.random() * range);
            }
        }; 
        for (int i = 0; i < ITERATION_COUNT; i++) {
            total += chisquare(N, R, qrRandomInt);
        }
        System.out.printf("Ave Chi2 for QR: %f \n", total / ITERATION_COUNT);        

        total = 0.0;
        RandomFunction mathRandomInt = new RandomFunction() {
            @Override
            public int randomint(int range) {
                return (int) (Math.random() * range);
            }
        };         
        for (int i = 0; i < ITERATION_COUNT; i++) {
            total += chisquare(N, R, mathRandomInt);
        }
        System.out.printf("Ave Chi2 for Math.random: %f \n", total / ITERATION_COUNT);
    }
}

J'ai obtenu le résultat suivant:

Ave Chi2 for QR: 108965,078640
Ave Chi2 for Math.random: 99988,629040

Ce 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

33
higuaro

J'ai mis en place ne 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é. C'est ce que vous voyez le plus lorsque seed1 est élevé et seed2 est faible. Dans quelques cas, je n'ai obtenu que 3 numéros.

Au mieux, votre algorithme a besoin d'être affiné.

14
gilly3

Si la fonction Math.Random() appelle le système d'exploitation pour obtenir l'heure du jour, vous ne pouvez pas la comparer à votre fonction. Votre fonction est un PRNG, alors que cette fonction vise des nombres aléatoires réels. Pommes et oranges.

Votre PRNG peut être rapide, mais il n'a pas suffisamment d'informations sur l'état pour atteindre une longue période avant qu'il ne se répète (et sa logique n'est pas suffisamment sophistiquée pour même atteindre les périodes qui sont possibles avec autant) informations d'état).

La période est la longueur de la séquence avant que votre PRNG 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 antérieur. 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 que la convergence dégénérée sur une séquence particulière qui se répète. Il peut également être indésirable 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 sur chaque appelez.

Jetez un œil au Twister de Mersenne 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 Twister de Mersenne) consiste à faire le tour du 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 de bits. Mais à chaque étape, l'algorithme se déplace également dans le tableau, brouillant le contenu un peu à la fois.

8
Kaz

Il existe de très nombreux générateurs de nombres pseudo aléatoires. Par exemple, ranarray de Knuth, le twister de Mersenne , ou recherchez des générateurs LFSR. Les "algorithmes séminariques" monumentaux de Knuth analysent la zone et proposent des générateurs congruentiels linéaires (simples à mettre en œuvre, rapides).

Mais je vous suggère de vous en tenir à Java.util.Random ou Math.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 ajoutez-les à un nombre vraiment aléatoire, soit à partir de votre système d'exploitation, soit à partir de random.org . Si cela est requis pour certaines applications où la sécurité est critique, vous devrez creuser vous-même. Et comme dans ce cas, vous ne devriez pas croire ce que certains carrés colorés avec des bits manquants jaillissent ici, je vais me taire maintenant.

7
vonbrand

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 trouvé, sauf si vous accédez à une seule instance Random à partir de plusieurs threads (parce que Random est synchronized).

Cependant, si cela vraiment est 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 horrible résultats (en fonction des paramètres initiaux).

Si vous voulez les mêmes nombres que la classe Random vous donne, seulement plus rapidement, vous pouvez vous débarrasser de la synchronisation là-dedans:

public class QuickRandom {

    private long seed;

    private static final long MULTIPLIER = 0x5DEECE66DL;
    private static final long ADDEND = 0xBL;
    private static final long MASK = (1L << 48) - 1;

    public QuickRandom() {
        this((8682522807148012L * 181783497276652981L) ^ System.nanoTime());
    }

    public QuickRandom(long seed) {
        this.seed = (seed ^ MULTIPLIER) & MASK;
    }

    public double nextDouble() {
        return (((long)(next(26)) << 27) + next(27)) / (double)(1L << 53);
    }

    private int next(int bits) {
        seed = (seed * MULTIPLIER + ADDEND) & MASK;
        return (int)(seed >>> (48 - bits));
    }

}

J'ai simplement pris le Java.util.Random code et supprimé la synchronisation qui se traduit par deux fois les performances par rapport à l'original sur mon Oracle HotSpot JVM 7u9. Il est toujours plus lent que votre QuickRandom, mais il donne des résultats beaucoup plus cohérents. Pour être précis, pour les mêmes valeurs seed et les applications à thread unique, cela donne le même nombres pseudo-aléatoires que le ferait la classe Random d'origine.


Ce code est basé sur le courant Java.util.Random dans OpenJDK 7 qui est autorisé sous 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 instance Random non synchronisée. Il y en a un aussi dans le JDK!

Regardez la classe Java 7 ThreadLocalRandom . Le code qu'il contient est presque identique à mon code ci-dessus. La classe est simplement un thread local isolé Random version 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 définir son seed manuellement.

Exemple d'utilisation:

Random random = ThreadLocalRandom.current();
7
Petr Janeček

'Aléatoire', c'est plus que d'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

Modifier:

OK, après avoir été trop précipité dans cette réponse, permettez-moi de répondre à la vraie raison pour laquelle votre code est plus rapide:

Depuis JavaDoc pour Math.Random ()

Cette méthode est correctement synchronisée pour permettre une utilisation correcte par plusieurs threads. Cependant, si de nombreux threads ont besoin de générer des nombres pseudo-aléatoires à un rythme élevé, cela peut réduire le conflit pour que chaque thread ait son propre générateur de nombres pseudo-aléatoires.

C'est probablement pourquoi votre code est plus rapide.

3
rolfl

Java.util.Random n'est pas très différent, un LCG de base décrit par Knuth. Cependant, il présente 2 principaux avantages/différences principaux:

  • thread safe - chaque mise à jour est un CAS qui est plus cher qu'une simple écriture et nécessite une branche (même si elle est parfaitement prédite single thread). Selon le CPU, il pourrait y avoir une différence significative.
  • état interne non divulgué - c'est très important pour tout ce qui n'est pas trivial. Vous souhaitez que les nombres aléatoires ne soient pas prévisibles.

Ci-dessous, c'est la routine principale générant des entiers "aléatoires" dans Java.util.Random.


  protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
          oldseed = seed.get();
          nextseed = (oldseed * multiplier + addend) & mask;
        } while (!seed.compareAndSet(oldseed, nextseed));
        return (int)(nextseed >>> (48 - bits));
    }

Si vous supprimez l'AtomicLong et l'état non divulgué (c'est-à-dire en utilisant tous les bits de long), vous obtiendrez plus de performances que la double multiplication/modulo.

Dernière note: Math.random ne doit pas être utilisé pour autre chose que des tests simples, il est sujet à controverse et si vous avez même quelques threads l'appelant simultanément, les performances se dégradent. Une caractéristique historique peu connue est l'introduction de CAS dans Java - pour battre une référence infâme (d'abord par IBM via intrinsics puis Sun a fait "CAS from Java")

3
bestsss

C'est la fonction aléatoire que j'utilise pour mes jeux. Il est assez rapide et a une bonne (assez) distribution.

public class FastRandom {

    public static int randSeed;

      public static final int random()
      {
        // this makes a 'nod' to being potentially called from multiple threads
        int seed = randSeed;

        seed    *= 1103515245;
        seed    += 12345;
        randSeed = seed;
        return seed;
      }

      public static final int random(int range)
      {
        return ((random()>>>15) * range) >>> 17;
      }

      public static final boolean randomBoolean()
      {
         return random() > 0;
      }

       public static final float randomFloat()
       {
         return (random()>>>8) * (1.f/(1<<24));
       }

       public static final double randomDouble() {
           return (random()>>>8) * (1.0/(1<<24));
       }
}
0
Terje