Является ли это "достаточно хорошим" случайным алгоритмом; почему он не используется, если он быстрее?

Я создал класс под названием QuickRandom, и его задача - быстро создавать случайные числа. Это очень просто: просто возьмите старое значение, умножьте на double и возьмите десятичную часть.

Вот мой класс QuickRandom в целом:

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

Это очень простой алгоритм, который просто умножает предыдущий двойной на "магическое число". Я быстро выбросил его, так что, возможно, я мог бы улучшить его, но, как ни странно, он работает нормально.

Это пример вывода прокомментированных строк в методе 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. Довольно случайный. Фактически, это будет работать для генератора случайных чисел в игре.

Вот пример вывода части без комментария:

5456313909
1427223941

Ничего себе! Он выполняет почти в 4 раза быстрее, чем Math.random.

Я помню, где-то читал, что Math.random использовал System.nanoTime() и тонны сумасшедшего модуля и деления. Это действительно необходимо? Мой алгоритм работает намного быстрее и кажется довольно случайным.

У меня есть два вопроса:

  • Является ли мой алгоритм "достаточно хорошим" (например, игра, где действительно случайные числа не слишком важны)?
  • Почему Math.random делает так много, когда кажется просто достаточным умножением и вырезанием десятичного числа будет достаточно?

Ответ 1

Ваша реализация QuickRandom не имеет равномерного распределения. Частоты обычно выше при более низких значениях, а Math.random() имеет более равномерное распределение. Здесь SSCCE, который показывает, что:

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

}

Средний результат выглядит следующим образом:

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  :###################    

Если вы повторите тест, вы увидите, что распределение QR сильно варьируется в зависимости от исходных семян, тогда как распределение MR стабильно. Иногда он достигает желаемого равномерного распределения, но чаще, чем это часто бывает. Здесь один из самых экстремальных примеров, он даже выходит за границы графика:

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  :                                                  

Ответ 2

То, что вы описываете, представляет собой случайный генератор, называемый линейным конгруэнтным генератором . Генератор работает следующим образом:

  • Начните с начального значения и множителя.
  • Чтобы создать случайное число:
    • Умножьте семя на множитель.
    • Установите семя, равное этому значению.
    • Вернуть это значение.

Этот генератор имеет много хороших свойств, но имеет значительные проблемы как хороший случайный источник. Статья Википедии, приведенная выше, описывает некоторые из сильных и слабых сторон. Короче говоря, если вам нужны хорошие случайные значения, это, вероятно, не очень хороший подход.

Надеюсь, это поможет!

Ответ 3

Ваша функция случайных чисел плохая, так как она имеет слишком мало внутреннего состояния - номер, выводимый функцией на любом данном шаге, полностью зависит от предыдущего номера. Например, если мы предположим, что magicNumber равно 2 (в качестве примера), то последовательность:

0.10 -> 0.20

сильно отражается в сходных последовательностях:

0.09 -> 0.18
0.11 -> 0.22

Во многих случаях это приведет к заметным корреляциям в вашей игре - например, если вы выполняете последовательные вызовы вашей функции для создания координат X и Y для объектов, объекты будут формировать четкие диагональные шаблоны.

Если у вас нет оснований полагать, что генератор случайных чисел замедляет ваше приложение (и это очень маловероятно), нет веских оснований, чтобы попытаться написать свой собственный.

Ответ 4

Реальная проблема заключается в том, что выведенная гистограмма зависит от начального затравки далеко от многого - большую часть времени она будет иметь почти однородный выход, но большая часть времени будет иметь явно неравномерный выход.

Вдохновленный в этой статье о том, как работает плохая функция php rand(), я сделал несколько случайных матричных изображений с использованием QuickRandom и System.Random, Этот прогон показывает, как иногда семя может иметь плохой эффект (в данном случае в пользу более низких чисел), где System.Random является довольно однородным.

QuickRandom

System.Random

Даже хуже /

Если мы инициализируем QuickRandom как new QuickRandom(0.01, 1.03), получим это изображение:

Код

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

Ответ 5

Одна проблема с генератором случайных чисел заключается в том, что нет "скрытого состояния" - если я знаю, какое случайное число вы вернули при последнем вызове, я знаю каждое случайное число, которое вы отправите до конца времени, так как там это только один возможный следующий результат и т.д. и т.д.

Еще одна вещь, которую следует учитывать, - это "период" вашего генератора случайных чисел. Очевидно, что с размером конечного состояния, равным части мантиссы двойника, он сможет возвращать не более 2 ^ 52 значений перед циклом. Но это в лучшем случае - можете ли вы доказать, что нет циклов периода 1, 2, 3, 4...? Если есть, ваш RNG будет иметь ужасное, вырожденное поведение в этих случаях.

Кроме того, будет ли ваше генерирование случайных чисел равномерным распределением для всех исходных точек? Если это не так, то ваш RNG будет предвзятым или, что еще хуже, предвзятым по-разному в зависимости от исходного семени.

Если вы можете ответить на все эти вопросы, это удивительно. Если вы не можете, то вы знаете, почему большинство людей не изобретают колесо и не используют проверенный генератор случайных чисел;)

(Кстати, хорошая пословица: самый быстрый код - это код, который не запускается. Вы можете сделать самый быстрый случайный() в мире, но это нехорошо, если он не очень случайный)

Ответ 6

Один общий тест, который я всегда делал при разработке PRNG, заключался в следующем:

  • Преобразовать вывод в char значения
  • Введите значение символа в файл
  • Сжать файл

Это позволило мне быстро перебирать идеи, которые были "достаточно хорошими" для PRNG для последовательностей от 1 до 20 мегабайт. Он также дал лучшую картину сверху вниз, чем просто осмотр ее на глаз, так как любой "хороший" PRNG с половинным словом состояния мог быстро превысить способность ваших глаз видеть точку цикла.

Если бы я был очень придирчив, я мог бы взять хорошие алгоритмы и запустить тесты DIEHARD/NIST на них, чтобы получить больше информации, а затем вернуться и настроить еще немного.

Преимущество теста сжатия, в отличие от частотного анализа, состоит в том, что тривиально легко построить хорошее распределение: просто выведите блок длиной 256, содержащий все символы значений 0 - 255, и сделайте это 100 000 раз. Но эта последовательность имеет цикл длины 256.

Скорректированное распределение, даже с небольшим отрывом, должно быть подхвачено алгоритмом сжатия, особенно если вы дадите ему достаточно (скажем, 1 мегабайт) последовательности для работы. Если некоторые символы, или bigrams или n-граммы встречаются чаще, алгоритм сжатия может кодировать этот перекос распределения кодам, который способствует частым появлениям с более короткими кодовыми словами, и вы получаете дельта сжатия.

Поскольку большинство алгоритмов сжатия бывают быстрыми, и они не требуют никакой реализации (поскольку ОС просто обнимают их), тест сжатия является очень полезным для быстрого оценки прохода/отказа для PRNG, который вы могли бы разработать.

Удачи вам в ваших экспериментах!

О, я выполнил этот тест по rng, который у вас выше, используя следующий небольшой код вашего кода:

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

Результаты:

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

Я бы подумал, что PRNG хорошо, если выходной файл вообще не может быть сжат. Честно говоря, я не думал, что ваш PRNG будет делать это хорошо, только 16% на ~ 20 мегабайтах довольно впечатляют для такой простой конструкции. Но я все еще считаю это неудачей.

Ответ 7

Самый быстрый генератор случайных чисел, который вы могли бы реализовать, это:

enter image description here

XD, шутки друг от друга, помимо всего сказанного здесь, я хотел бы внести свой вклад в цитирование что тестирование случайных последовательностей "является трудной задачей" [1], и существует несколько тестов которые проверяют определенные свойства псевдослучайных чисел, их можно найти много здесь: http://www.random.org/analysis/#2005

Один простой способ оценить случайное генераторное "качество" - это старое испытание Квадрата Чи.

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

Цитирование [1]

Идея теста χ² состоит в том, чтобы проверить, являются ли произведенные числа распространены разумно. Если мы породим N положительных чисел, меньших r, то мы бы ожидайте получить около N/r номеров каждого значения. Но --- и в этом суть вопрос --- частоты появления всех значений не должны быть точно то же: это не было бы случайным!

Мы просто вычислим сумму квадратов частот возникновения каждое значение, масштабируемое по ожидаемой частоте, а затем вычесть размер последовательность. Это число, "статистика χ²", может быть выражено математически как

chi squared formula

Если статистика χ² близка к r, то числа являются случайными; если он слишком далеко, то это не так. Понятия "близко" и "далеко" могут быть более точными определены: существуют таблицы, которые точно определяют, как соотносить статистику с свойствами случайные последовательности. Для простого теста, который мы выполняем, статистика должна быть в пределах 2√r

Используя эту теорию и следующий код:

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

Я получил следующий результат:

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

Что для QuickRandom находится далеко от r (вне r ± 2 * sqrt(r))

Было сказано, что QuickRandom может быть быстрым, но (как указано в других ответах) не является хорошим генератором случайных чисел


[1] SEDGEWICK ROBERT, Алгоритмы в C, Addinson Wesley Publishing Company, 1990, с. 516 по 518

Ответ 8

Я собрал быстрый макет вашего алгоритма в JavaScript для оценки результатов. Он генерирует 100 000 случайных чисел от 0 до 99 и отслеживает экземпляр каждого целого числа.

Первое, что я замечаю, это то, что вы, скорее всего, получите меньшее число, чем большое число. Вы видите это больше всего, когда seed1 высокий, а seed2 - низкий. В нескольких случаях у меня было всего 3 номера.

В лучшем случае ваш алгоритм нуждается в уточнении.

Ответ 9

Если функция Math.Random() вызывает операционную систему для получения времени суток, вы не можете сравнить ее с вашей функцией. Ваша функция - PRNG, тогда как эта функция стремится к реальным случайным числам. Яблоки и апельсины.

Ваш PRNG может быть быстрым, но он не имеет достаточной информации о состоянии для достижения длительного периода, прежде чем он повторится (и его логика недостаточно сложна, чтобы даже достичь периодов, которые возможны с такой информацией о состоянии).

Период - это длина последовательности до того, как ваш PRNG начнет повторяться. Это происходит, как только машина PRNG переводит состояние в состояние, которое идентично некоторому состоянию прошлого. Оттуда он повторит переходы, которые начались в этом состоянии. Другой проблемой с PRNG может быть небольшое количество уникальных последовательностей, а также вырожденная конвергенция на определенной последовательности, которая повторяется. Могут также быть нежелательные шаблоны. Например, предположим, что PRNG выглядит довольно случайным, когда числа печатаются в десятичной форме, но проверка значений в двоичном выражении показывает, что бит 4 просто переключается между 0 и 1 для каждого вызова. К сожалению,

Взгляните на Mersenne Twister и другие алгоритмы. Существуют способы достижения баланса между длительностью периода и циклами ЦП. Один базовый подход (используемый в Mersenne Twister) состоит в том, чтобы циклически перемещаться в векторе состояния. То есть, когда число генерируется, оно не основано на целом состоянии, а всего лишь на нескольких словах из массива состояний, подчиненных нескольким битовым операциям. Но на каждом шаге алгоритм также перемещается в массиве, немного скремблируя содержимое за раз.

Ответ 10

Есть много, много генераторов псевдослучайных чисел. Например, Knuth ranarray, Mersenne twister или искать генераторы LFSR. Монументальные "Семинумерные алгоритмы" Кнута анализируют область и предлагают некоторые линейные конгруэнтные генераторы (простые для реализации, быстрые).

Но я бы посоветовал вам просто придерживаться java.util.Random или Math.random, они быстрей и, по крайней мере, ОК для случайного использования (т.е. игр и т.д.). Если вы просто параноик в распределении (какая-то программа Монте-Карло или генетический алгоритм), проверьте их реализацию (источник доступен где-то) и заселите их по-настоящему случайным числом, либо из вашей операционной системы, либо из random.org. Если это требуется для некоторых приложений, где важна безопасность, вам придется копать себя. И так как в этом случае вы не должны поверить, что здесь есть какой-то цветной квадрат с отсутствующими битами, я закрою сейчас.

Ответ 11

Очень маловероятно, что производительность генерации случайных чисел была бы проблемой для любого случая использования, с которым вы столкнулись, если не получить доступ к одному экземпляру Random из нескольких потоков (поскольку Random - synchronized).

Однако, если это действительно так, и вам нужно много случайных чисел быстро, ваше решение слишком ненадежно. Иногда это дает хорошие результаты, иногда это дает ужасные результаты (основанные на начальных настройках).

Если вам нужны те же номера, что класс Random дает вам только быстрее, вы можете избавиться от синхронизации там:

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

}

Я просто взял java.util.Random код и удалил синхронизацию, которая приводит к удвоению производительности по сравнению с оригиналом на моем Oracle HotSpot JVM 7u9, Он все еще медленнее, чем ваш QuickRandom, но он дает гораздо более последовательные результаты. Точнее, для тех же значений seed и однопоточных приложений он дает те же псевдослучайные числа, что и исходный класс Random.


Этот код основан на текущем java.util.Random в OpenJDK 7u, который лицензируется под GNU GPL v2.


EDIT 10 месяцев спустя:

Я только что обнаружил, что вам даже не нужно использовать мой код для получения несинхронизированного экземпляра Random. Там тоже в JDK!

Посмотрите на класс Java 7 ThreadLocalRandom. Код внутри него почти идентичен моему коду выше. Класс является просто изолированной локальным потоком версией Random, подходящей для быстрого генерации случайных чисел. Единственным недостатком, о котором я могу думать, является то, что вы не можете вручную установить его seed.

Пример использования:

Random random = ThreadLocalRandom.current();

Ответ 12

"Случайный" - это больше, чем просто получение чисел.... у вас есть псевдослучайный

Если псевдослучайность достаточно хороша для ваших целей, то уверен, что она быстрее (и XOR + Bitshift будет быстрее, чем у вас)

Рольф

Edit:

Хорошо, поспешив в этом ответе, позвольте мне ответить на настоящую причину, по которой ваш код быстрее:

Из JavaDoc для Math.Random()

Этот метод правильно синхронизирован, чтобы обеспечить правильное использование несколькими потоками. Однако, если многие потоки должны генерировать псевдослучайные числа с большой скоростью, это может уменьшить конкуренцию для каждого потока, чтобы иметь свой собственный генератор псевдослучайных чисел.

Скорее всего, ваш код быстрее.

Ответ 13

java.util.Random не сильно отличается, базовый LCG, описанный Кнутом. Однако он имеет основные 2 основных преимущества/отличия:

  • потокобезопасное - каждое обновление - это CAS, который дороже, чем простая запись, и ему нужна ветка (даже если она точно предсказана одиночной резьбой). В зависимости от процессора это может быть значительная разница.
  • нераскрытое внутреннее состояние - это очень важно для чего-то нетривиального. Вы хотите, чтобы случайные числа не были предсказуемыми.

Ниже его основная процедура генерирует "случайные" целые числа в 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));
    }

Если вы удалите AtomicLong и нераскрытое содержимое (т.е. используя все биты long), вы получите большую производительность, чем двойное умножение/модулю.

Последнее примечание: Math.random не должно использоваться ни для чего, кроме простых тестов, оно склонно к конфликту, и если у вас даже пара потоков, вызывающих его одновременно, производительность ухудшается. Одной из немногих известных исторических особенностей этого является введение CAS в java - избиение печально известного теста (сначала IBM через intrinsics, а затем Sun сделала CAS с Java)

Ответ 14

Это случайная функция, которую я использую для своих игр. Это довольно быстро и имеет хорошее (достаточное) распределение.

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