Я создал класс с именем QuickRandom
, и его задача - быстро генерировать случайные числа. Это действительно просто: просто возьмите старое значение, умножьте на a 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
Гектометр Довольно случайно. Фактически, это сработало бы для генератора случайных чисел в игре.
Вот пример вывода некомментированной части:
5456313909
1427223941
Вот Это Да! Он работает почти в 4 раза быстрее, чемMath.random
.
Я помню, как читал где-то, что Math.random
использовало System.nanoTime()
множество тонных модулей и делений Это действительно необходимо? Мой алгоритм работает намного быстрее и кажется довольно случайным.
У меня есть два вопроса:
- Является ли мой алгоритм «достаточно хорошим» (например, для игры, где действительно случайные числа не слишком важны)?
- Почему
Math.random
так много, когда кажется, что достаточно простого умножения и вырезания десятичной дроби?
источник
new QuickRandom(0,5)
илиnew QuickRandom(.5, 2)
. Они оба будут неоднократно выводить 0 для вашего номера.Ответы:
Ваша
QuickRandom
реализация на самом деле не имеет равномерного распределения. Частоты обычно выше при более низких значениях, в то время какMath.random()
имеет более равномерное распределение. Вот SSCCE, который показывает, что:Средний результат выглядит так:
Если вы повторите тест, вы увидите, что распределение QR сильно варьируется, в зависимости от начальных семян, в то время как распределение MR стабильно. Иногда оно достигает желаемого равномерного распределения, но чаще всего этого не происходит. Вот один из самых экстремальных примеров, он выходит за границы графика:
источник
QuickRandom
. Иногда это близко к форме, иногда намного хуже, чем это.То, что вы описываете, является типом случайного генератора, называемого линейным конгруэнтным генератором . Генератор работает следующим образом:
Этот генератор имеет много хороших свойств, но имеет значительные проблемы как хороший случайный источник. В статье Википедии, ссылки на которую приведены выше, описаны некоторые сильные и слабые стороны. Короче говоря, если вам нужны хорошие случайные значения, это, вероятно, не очень хороший подход.
Надеюсь это поможет!
источник
Ваша функция случайных чисел плохая, так как имеет слишком мало внутреннего состояния - число, выводимое функцией на любом данном шаге, полностью зависит от предыдущего числа. Например, если мы предположим, что
magicNumber
это 2 (в качестве примера), то последовательность:сильно отражается в похожих последовательностях:
Во многих случаях это создаст заметные корреляции в вашей игре - например, если вы выполняете последовательные вызовы своей функции для генерации координат X и Y для объектов, объекты будут образовывать четкие диагональные структуры.
Если у вас нет веских оснований полагать, что генератор случайных чисел замедляет работу вашего приложения (а это ОЧЕНЬ маловероятно), нет веских оснований пытаться написать свое собственное.
источник
Реальная проблема с этим заключается в том, что его выходная гистограмма во многом зависит от начального начального числа - большую часть времени она будет иметь почти равномерный выходной сигнал, но большую часть времени будет иметь явно неоднородный выходной сигнал.
Вдохновленный этой статьей о том, насколько плоха
rand()
функция php , я сделал несколько случайных матричных изображений, используяQuickRandom
иSystem.Random
. Этот прогон показывает, как иногда начальное число может иметь плохой эффект (в данном случае предпочтение меньшим числам), когдаSystem.Random
оно довольно равномерно.QuickRandom
System.Random
Еще хуже
Если мы инициализируем,
QuickRandom
какnew QuickRandom(0.01, 1.03)
мы получаем это изображение:Код
источник
magicNumber
умножение производит число, похожее наprevNum
, что показывает отсутствие случайности. Если мы используем семена,new QuickRandom(0.01, 1.03)
то мы получим это i.imgur.com/Q1Yunbe.png !System.Drawing.Bitmap
.Одна проблема с вашим генератором случайных чисел заключается в том, что не существует «скрытого состояния» - если я знаю, какое случайное число вы вернули при последнем звонке, я знаю каждое случайное число, которое вы отправите до конца времени, поскольку существует только одно возможен следующий результат и тд и тп.
Еще одна вещь, которую нужно учитывать, это период вашего генератора случайных чисел. Очевидно, что с конечным размером состояния, равным порции двойного мантиссы, он сможет возвращать самое большее 2 ^ 52 значений перед циклом. Но это в лучшем случае - можете ли вы доказать, что нет циклов периода 1, 2, 3, 4 ...? Если это так, ваш ГСЧ будет иметь ужасное, вырожденное поведение в этих случаях.
Кроме того, будет ли ваше поколение случайных чисел иметь равномерное распределение для всех начальных точек? Если этого не произойдет, то ваш RNG будет смещен - или хуже, смещен по-разному в зависимости от исходного семени.
Если вы можете ответить на все эти вопросы, круто. Если вы не можете, то вы знаете, почему большинство людей не изобретают велосипед и не используют проверенный генератор случайных чисел;)
(Кстати, хорошая пословица такова: самый быстрый код - это код, который не запускается. Вы можете сделать самый быстрый случайный () в мире, но это не хорошо, если он не очень случайный)
источник
0 -> 0
. В зависимости от семени, может быть много других. (Так , например, с семенем 3,0,0.5 -> 0.5
,0.25 -> 0.75 -> 0.25
,0.2 -> 0.6 -> 0.8 -> 0.4 -> 0.2
и т.д.)Один общий тест, который я всегда делал при разработке PRNG, был:
Это позволило мне быстро перейти к идеям, которые были «достаточно хорошими» PRNG для последовательностей от 1 до 20 мегабайт. Это также дало лучшую картину сверху вниз, чем просто визуальный осмотр, так как любой «достаточно хороший» PRNG с половиной слова состояния мог бы быстро превзойти способность вашего глаза видеть точку цикла.
Если бы я был действительно придирчив, я мог бы взять хорошие алгоритмы и запустить на них тесты DIEHARD / NIST, чтобы получить больше понимания, а затем вернуться и немного доработать.
Преимущество теста на сжатие по сравнению с частотным анализом состоит в том, что тривиально легко построить хорошее распределение: просто вывести блок длиной 256, содержащий все символы со значениями 0 - 255, и сделать это 100 000 раз. Но эта последовательность имеет цикл длиной 256.
Перекошенное распределение, даже с небольшим запасом, должно быть подобрано алгоритмом сжатия, особенно если вы даете ему достаточно (скажем, 1 мегабайт) последовательности для работы. Если некоторые символы, или биграммы, или n-граммы встречаются чаще, алгоритм сжатия может закодировать это распределение с перекосом в коды, которые поддерживают частые вхождения с более короткими кодовыми словами, и вы получите дельту сжатия.
Поскольку большинство алгоритмов сжатия являются быстрыми и не требуют реализации (так как в ОС они просто лежат), тест на сжатие очень полезен для быстрой оценки прохождения / отказа для ГСЧ, которую вы разрабатываете.
Удачи в ваших экспериментах!
О, я выполнил этот тест на вашей программе, используя следующий небольшой мод вашего кода:
Результаты были:
Я считаю, что PRNG хорош, если выходной файл не может быть сжат вообще. Если честно, я не думал, что ваш PRNG будет так хорош, только 16% на ~ 20 мегабайт довольно впечатляюще для такой простой конструкции. Но я все еще считаю это неудачей.
источник
Самый быстрый генератор случайных чисел, который вы могли бы реализовать, это:
XD, кроме шуток, помимо всего сказанного здесь, я хотел бы внести свой вклад, сославшись на то, что тестирование случайных последовательностей "является сложной задачей" [1], и есть несколько тестов, которые проверяют определенные свойства псевдослучайных чисел, вы можете найти их много здесь: http://www.random.org/analysis/#2005
Один простой способ оценить «качество» генератора случайных чисел - это старый тест Хи-квадрат.
Ссылаясь [1]
Используя эту теорию и следующий код:
Я получил следующий результат:
Который, для QuickRandom, далеко от r (вне
r ± 2 * sqrt(r)
)Тем не менее, QuickRandom может быть быстрым, но (как указано в других ответах) не годится в качестве генератора случайных чисел
[1] Седжевик Роберт, Алгоритмы на С , Addinson Wesley Publishing Company, 1990, страницы с 516 по 518
источник
int[]
инициализируются на ноль, поэтому нет необходимости в этой части. Бросать на плаву бессмысленно, когда вы работаете с двойниками. Последнее: вызывать имена методов random1 и random2 довольно забавно.Я собрал краткий макет вашего алгоритма на JavaScript, чтобы оценить результаты. Он генерирует 100 000 случайных целых чисел от 0 до 99 и отслеживает экземпляр каждого целого числа.
Первое, что я замечаю, это то, что у вас больше шансов получить меньшее число, чем большое. Вы видите это больше всего, когда
seed1
высоко иseed2
низко. В нескольких случаях я получил только 3 номера.В лучшем случае ваш алгоритм нуждается в доработке.
источник
Если
Math.Random()
функция вызывает операционную систему, чтобы узнать время суток, вы не можете сравнить ее с вашей функцией. Ваша функция является PRNG, тогда как эта функция стремится к действительным случайным числам. Яблоки и апельсины.Ваш PRNG может быть быстрым, но у него недостаточно информации о состоянии, чтобы достичь длительного периода перед повторением (а его логика недостаточно сложна, чтобы даже достичь периодов, которые возможны с таким большим количеством информации о состоянии).
Период - это длина последовательности до того, как ваш PRNG начнет повторяться. Это происходит, как только машина PRNG переходит в состояние, идентичное некоторому прошлому состоянию. Оттуда он будет повторять переходы, которые начались в этом состоянии. Другая проблема с PRNG может заключаться в небольшом количестве уникальных последовательностей, а также в вырожденной сходимости на определенной последовательности, которая повторяется. Также могут быть нежелательные шаблоны. Например, предположим, что PRNG выглядит довольно случайным, когда числа печатаются в десятичном виде, но проверка значений в двоичном формате показывает, что бит 4 просто переключается между 0 и 1 при каждом вызове. К сожалению!
Взгляните на Mersenne Twister и другие алгоритмы. Есть способы найти баланс между продолжительностью периода и циклами ЦП. Один из основных подходов (используется в Twister Mersenne) заключается в циклическом переключении вектора состояния. То есть, когда число генерируется, оно не основано на полном состоянии, только на нескольких словах из массива состояний, подвергаемых нескольким битовым операциям. Но на каждом этапе алгоритм также перемещается в массиве, разбирая содержимое по частям.
источник
/dev/random
это источник реальной случайности, получаемый от драйверов устройств, а не PRNG. Блокируется, когда недостаточно битов доступно. Дочернее устройство/dev/urandom
также не блокирует, но все еще не является PRNG, поскольку оно обновляется случайными битами, когда они доступны.nanoTime()
+ counter / hash используются по умолчанию для семениjava.util.Random
oracle / OpenJDK. Это только для семян, тогда это стандартный LCG. По сути, генератор OP берет 2 случайных числа для начального числа, что нормально - так что никакой разницы, чемjava.util.Random
.System.currentTimeMillis()
был семенем по умолчанию в JDK1.4-Существует множество генераторов псевдослучайных чисел. Например, ранаррай Кнута , твистер Мерсенна или поиск генераторов LFSR. Монументальные «Получисленные алгоритмы» Кнута анализируют область и предлагают некоторые линейные конгруэнтные генераторы (простые в реализации, быстрые).
Но я бы посоветовал вам просто придерживаться
java.util.Random
илиMath.random
, они быстро и по крайней мере хорошо для случайного использования (например, игры и тому подобное). Если вы просто параноик по дистрибутиву (какая-то программа Монте-Карло или генетический алгоритм), проверьте их реализацию (где-то источник доступен) и начните с какого-то действительно случайного числа, либо из вашей операционной системы, либо из random.org. , Если это требуется для какого-либо приложения, где безопасность имеет решающее значение, вам придется копать самостоятельно. И поскольку в этом случае вы не должны верить тому, что здесь изображен какой-то цветной квадрат с недостающими битами, я сейчас заткнусь.источник
Маловероятно, что производительность генерации случайных чисел будет проблемой для любого варианта использования, который вы придумали, если только не получить доступ к одному
Random
экземпляру из нескольких потоков (потому чтоRandom
естьsynchronized
).Однако, если это действительно так и вам нужно много случайных чисел, ваше решение слишком ненадежно. Иногда это дает хорошие результаты, иногда это дает ужасные результаты (на основе начальных настроек).
Если вы хотите получить те же числа, что и
Random
класс, только быстрее, вы можете избавиться от синхронизации:Я просто взял
java.util.Random
код и удалить синхронизации, результаты которого в два раза производительность по сравнению с оригиналом на моем Oracle HotSpot JVM 7u9. Это все еще медленнее, чем у васQuickRandom
, но дает гораздо более последовательные результаты. Чтобы быть точным, для тех жеseed
значений и однопоточных приложений, он дает те же псевдослучайные числа, что и исходныйRandom
класс.Этот код основан на текущей
java.util.Random
версии OpenJDK 7u, которая лицензирована под GNU GPL v2 .РЕДАКТИРОВАТЬ 10 месяцев спустя:
Я только что обнаружил, что вам даже не нужно использовать мой код выше, чтобы получить несинхронизированный
Random
экземпляр. В JDK тоже есть один!Посмотрите на
ThreadLocalRandom
класс Java 7 . Код внутри него почти идентичен моему коду выше. Класс представляет собой просто изолированную от локального потокаRandom
версию, подходящую для быстрой генерации случайных чисел. Единственный недостаток, о котором я могу думать, это то, что вы не можете установить егоseed
вручную.Пример использования:
источник
:)
Это интересно, спасибо!«Случайный» - это больше, чем просто получение чисел… псевдослучайным
Если псевдослучайность достаточно хороша для ваших целей, то, конечно, она намного быстрее (и XOR + Bitshift будет быстрее, чем у вас)
Рольф
Редактировать:
Хорошо, поспешно ответив на этот вопрос, позвольте мне ответить на реальную причину, по которой ваш код работает быстрее:
Из JavaDoc для Math.Random ()
Вероятно, поэтому ваш код работает быстрее.
источник
Math.random()
медленнее. Он должен был бы либо синхронизироваться, либо создавать новыйRandom
каждый раз, и ни один из них не был бы очень привлекательным с точки зрения производительности. Если бы я заботился о производительности, я бы создал свою собственнуюnew Random
и просто использовал ее. : PСлучайность не сильно отличается, базовый LCG описан Кнутом. Однако у него есть два основных преимущества / отличия:
Ниже это основная процедура, генерирующая «случайные» целые числа в java.util.Random.
Если вы удалите AtomicLong и нераскрытое состояние (т.е. используя все биты
long
), вы получите большую производительность, чем двойное умножение / по модулю.Последнее замечание:
Math.random
его не следует использовать ни для чего, кроме простых тестов, оно подвержено конфликтам и если у вас есть даже несколько потоков, вызывающих его одновременно, производительность снижается. Одна малоизвестная историческая особенность - это внедрение CAS в java - чтобы преодолеть печально известный тест (сначала IBM через встроенные функции, а затем Sun сделала «CAS from Java»)источник
Это случайная функция, которую я использую для своих игр. Это довольно быстро и имеет хорошее (достаточно) распространение.
источник
volatile
нее компилятор может по своему усмотрению исключать (или вводить) локальные переменные.