Можно ли заполнить генератор случайных чисел (Math.random
) в JavaScript?
Заполнение генератора случайных чисел в Javascript
Ответы (16)
Нет, засеять Math.random()
невозможно, но довольно легко написать собственный генератор или, что еще лучше, использовать уже существующий.
Проверьте: этот связанный вопрос.
Кроме того, дополнительную информацию о раздаче см. Также в блоге Дэвида Бау.
Я реализовал ряд хороших, коротких и быстрых функций генератора псевдослучайных чисел (PRNG) на простом JavaScript. Все они могут быть посеяны и дают качественные числа.
Прежде всего, позаботьтесь о том, чтобы правильно инициализировать ваши PRNG. Чтобы упростить задачу, приведенные ниже генераторы не имеют встроенной процедуры генерации начального числа, но принимают одно или несколько 32-битных значений в качестве начальных начальное состояние ГПСЧ. Подобные или редкие начальные числа (например, простое начальное число из 1 и 2) имеют низкую энтропию и могут вызывать корреляции или другие проблемы с качеством, в результате чего выходные данные имеют аналогичные свойства (например, одинаковые случайно сгенерированные уровни). Чтобы избежать этого, рекомендуется инициализировать ГПСЧ с хорошо распределенным начальным значением с высокой энтропией.
К счастью, хеш-функции очень хорошо генерируют начальные числа из коротких строк. Хорошая хеш-функция даст очень разные результаты, даже если две строки похожи. Вот пример генератора начального числа, основанного на функции смешивания MurmurHash3:
function xmur3(str) {
for(var i = 0, h = 1779033703 ^ str.length; i < str.length; i++)
h = Math.imul(h ^ str.charCodeAt(i), 3432918353),
h = h << 13 | h >>> 19;
return function() {
h = Math.imul(h ^ h >>> 16, 2246822507);
h = Math.imul(h ^ h >>> 13, 3266489909);
return (h ^= h >>> 16) >>> 0;
}
}
Каждый последующий вызов функции возврата xmur3
создает новое 32-битное хеш-значение, которое будет использоваться в качестве начального числа в ГПСЧ. Вот как это можно использовать:
// Create xmur3 state:
var seed = xmur3("apples");
// Output four 32-bit hashes to provide the seed for sfc32.
var rand = sfc32(seed(), seed(), seed(), seed());
// Output one 32-bit hash to provide the seed for mulberry32.
var rand = mulberry32(seed());
// Obtain sequential random numbers like so:
rand();
rand();
Однако это только одно из возможных решений. В качестве альтернативы, просто выберите фиктивные данные для заполнения начального числа и продвиньте генератор несколько раз (12-20 итераций), чтобы тщательно перемешать начальное состояние. Это часто наблюдается в эталонных реализациях ГПСЧ, но это ограничивает количество начальных состояний:
var seed = 1337 ^ 0xDEADBEEF; // 32-bit seed with optional XOR value
// Pad seed with Phi, Pi and E.
// https://en.wikipedia.org/wiki/Nothing-up-my-sleeve_number
var rand = sfc32(0x9E3779B9, 0x243F6A88, 0xB7E15162, seed);
for (var i = 0; i < 15; i++) rand();
На выходе этих функций PRNG получается положительное 32-битное число (от 0 до 2 32 -1), которое затем преобразуется в число с плавающей запятой в диапазоне от 0 до 1 (0 включительно, 1 исключающий) эквивалент на Math.random()
, если вам нужны случайные числа из определенного диапазона, прочтите эту статью о MDN. Если вам нужны только необработанные биты, просто удалите последнюю операцию деления.
Примечание. Числа JavaScript могут представлять только целые числа с разрешением до 53 бит. А при использовании побитовых операций это число уменьшается до 32. Современные ГПСЧ на других языках часто используют 64-битные операции, для которых при переносе на JS требуются прокладки, которые могут резко снизить производительность. Алгоритмы здесь используют только 32-битные операции, так как они напрямую совместимы с JS.
Теперь перейдем к генераторам. (Я веду полный список со ссылками и информацией о лицензии здесь а>)
sfc32 (простой быстрый счетчик)
sfc32 является частью пакета тестирования случайных чисел PractRand (который, конечно же, проходит) . sfc32 имеет 128-битное состояние и работает очень быстро в JS.
function sfc32(a, b, c, d) {
return function() {
a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0;
var t = (a + b) | 0;
a = b ^ b >>> 9;
b = c + (c << 3) | 0;
c = (c << 21 | c >>> 11);
d = d + 1 | 0;
t = t + d | 0;
c = c + t | 0;
return (t >>> 0) / 4294967296;
}
}
Шелковица32
Mulberry32 - простой генератор с 32-битным состоянием, но он чрезвычайно быстр и имеет хорошее качество (автор утверждает, что он проходит все тесты gjrand и имеет полный период 2 32, но я не проверял).
function mulberry32(a) {
return function() {
var t = a += 0x6D2B79F5;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
}
}
Я бы порекомендовал это, если вам просто нужен простой, но приличный ГПСЧ и не нужны миллиарды случайных чисел (см. Проблема дня рождения).
xoshiro128 **
По состоянию на май 2018 г. xoshiro128 ** является новым членом семейства Xorshift от Vigna & Blackman (профессор Винья также отвечал за алгоритм Xorshift128 +, который поддерживает большинство Math.random
реализаций под капотом). Это самый быстрый генератор, который предлагает 128-битное состояние.
function xoshiro128ss(a, b, c, d) {
return function() {
var t = b << 9, r = a * 5; r = (r << 7 | r >>> 25) * 9;
c ^= a; d ^= b;
b ^= c; a ^= d; c ^= t;
d = d << 11 | d >>> 21;
return (r >>> 0) / 4294967296;
}
}
Авторы утверждают, что он хорошо проходит тесты на случайность (хотя и с оговорками). Другие исследователи отметили, что он не проходит некоторые тесты в TestU01 (в частности, LinearComp и BinaryRank). На практике это не должно вызывать проблем при использовании чисел с плавающей запятой (например, в этих реализациях), но может вызвать проблемы, если полагаться на необработанные младшие биты.
JSF (Маленький пост Дженкинса)
Это JSF или smallprng Боба Дженкинса (2007), который также сделал ISAAC и SpookyHash. Он проходит тесты PractRand и должен быть довольно быстро, хотя и не так быстро, как sfc32.
function jsf32(a, b, c, d) {
return function() {
a |= 0; b |= 0; c |= 0; d |= 0;
var t = a - (b << 27 | b >>> 5) | 0;
a = b ^ (c << 17 | c >>> 15);
b = c + d | 0;
c = d + t | 0;
d = a + t | 0;
return (d >>> 0) / 4294967296;
}
}
seed = (seed * 185852 + 1) % 34359738337
.
- person Lachmanski; 11.06.2019
Math.imul
позволяет ему переполниться, как при умножении в C на 32-битные целые числа. Вы предлагаете LCG, использующую весь диапазон целочисленного пространства JS, что, безусловно, является интересной областью для изучения. :)
- person bryc; 11.06.2019
| 0
в firefox: jsbench.me/afk9jv36oz/ 1.
- person bryc; 28.04.2020
n = Math.floor(rand() * X); // generate random integer below X
? Для выбора алгоритма, например JSF говорит, что он работает хорошо (другие работают без проблем, так лучше?) И должен быть быстрым. Я вижу, что в git у вас есть числа, это может помочь быть более конкретным. Так или иначе, ваш ответ мне помог, спасибо за работу!
- person Luc; 01.05.2020
xmur3
действительно коррелирован, это нельзя отрицать - я имел в виду связанную реализацию 128-битного хеша, которая не была коррелирована. К сожалению, исправление xmur3 потребовало бы увеличения размера кода в четыре раза. Какой 128-битный хеш вы используете? Я не видел многих из них в JS.
- person bryc; 14.10.2020
ПРИМЕЧАНИЕ. Несмотря на (а точнее, из-за) лаконичности и кажущейся элегантности, этот алгоритм никоим образом не является высококачественным с точки зрения случайности. Ищите, например, те, которые перечислены в этом ответе для получения лучших результатов.
(Первоначально адаптировано из умной идеи, представленной в комментарии к другому ответу.)
var seed = 1;
function random() {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
Вы можете установить seed
на любое число, просто избегайте нуля (или любого числа, кратного Math.PI).
На мой взгляд, элегантность этого решения заключается в отсутствии каких-либо «магических» чисел (кроме 10000, что представляет собой минимальное количество цифр, которое вы должны выбросить, чтобы избежать странных шаблонов - смотрите результаты со значениями 10, 100, 1000). Краткость тоже хороша.
Это немного медленнее, чем Math.random () (в 2 или 3 раза), но я считаю, что он примерно так же быстр, как любое другое решение, написанное на JavaScript.
Нет, но вот простой генератор псевдослучайных чисел, реализация Умножение с переносом Я адаптировал из Википедии (с тех пор удален):
var m_w = 123456789;
var m_z = 987654321;
var mask = 0xffffffff;
// Takes any integer
function seed(i) {
m_w = (123456789 + i) & mask;
m_z = (987654321 - i) & mask;
}
// Returns number between 0 (inclusive) and 1.0 (exclusive),
// just like Math.random().
function random()
{
m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask;
m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask;
var result = ((m_z << 16) + (m_w & 65535)) >>> 0;
result /= 4294967296;
return result;
}
РЕДАКТИРОВАТЬ: исправлена начальная функция путем сброса m_z
EDIT2: исправлены серьезные недостатки реализации
seed
не сбрасывает генератор случайных чисел, потому что переменная mz_z
изменяется при вызове random()
. Поэтому установите mz_z = 987654321
(или любое другое значение) в seed
- person Michael_Scharf; 21.07.2014
m_w
, а не m_z
. 2) И m_w
, и m_z
изменяются НА ОСНОВЕ своих предыдущих значений, поэтому результат изменяется.
- person ESL; 17.09.2015
3.469446951953614e-18
, т. Е. чрезвычайно хорошо распределена. Это сценарий, который я использовал для тестирования распространения. Просто нужен объект с next()
методом.
- person Qix - MONICA WAS MISTREATED; 12.06.2017
Алгоритм Антти Сюкари красивый и короткий. Сначала я сделал вариант, который заменил Math.random
в JavaScript при вызове Math.seed(s)
, но затем Джейсон прокомментировал, что было бы лучше вернуть функцию:
Math.seed = function(s) {
return function() {
s = Math.sin(s) * 10000; return s - Math.floor(s);
};
};
// usage:
var random1 = Math.seed(42);
var random2 = Math.seed(random1());
Math.random = Math.seed(random2());
Это дает вам еще одну функциональность, которой нет в JavaScript: несколько независимых генераторов случайных чисел. Это особенно важно, если вы хотите, чтобы одновременно выполнялось несколько повторяемых симуляций.
Math.random
, это позволит вам иметь несколько независимых генераторов, верно?
- person Jason Goemaat; 29.05.2014
Math.seed(42);
, функция сбрасывается, поэтому если вы сделаете var random = Math.seed(42); random(); random();
, вы получите 0.70...
, а затем 0.38...
. Если вы сбросите его, позвонив var random = Math.seed(42);
еще раз, то в следующий раз, когда вы позвоните random()
, вы снова получите 0.70...
, а в следующий раз снова получите 0.38...
.
- person WOUNDEDStevenJones; 07.12.2017
random
вместо перезаписи встроенной функции javascript. Перезапись Math.random
может привести к тому, что компилятор JIST не будет оптимизировать весь ваш код.
- person Jack Giffin; 13.04.2018
Пожалуйста, посмотрите работы Пьера Л'Экуйера, относящиеся к концу 1980-х - началу 1990-х годов. Есть и другие. Самостоятельное создание (псевдо) генератора случайных чисел, если вы не являетесь экспертом, довольно опасно, потому что высока вероятность того, что либо результаты не будут статистически случайными, либо будут иметь небольшой период. Пьер (и другие) собрали несколько хороших (псевдо) генераторов случайных чисел, которые легко реализовать. Я использую один из его генераторов LFSR.
https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf
Фил Трой
Комбинируя некоторые из предыдущих ответов, это та случайная функция, которую вы ищете:
Math.seed = function(s) {
var mask = 0xffffffff;
var m_w = (123456789 + s) & mask;
var m_z = (987654321 - s) & mask;
return function() {
m_z = (36969 * (m_z & 65535) + (m_z >>> 16)) & mask;
m_w = (18000 * (m_w & 65535) + (m_w >>> 16)) & mask;
var result = ((m_z << 16) + (m_w & 65535)) >>> 0;
result /= 4294967296;
return result;
}
}
var myRandomFunction = Math.seed(1234);
var randomNumber = myRandomFunction();
Math.seed(0)()
возвращает 0.2322845458984375
, а Math.seed(1)()
возвращает 0.23228873685002327
. Кажется, помогает изменение m_w
и m_z
в зависимости от семени. var m_w = 987654321 + s; var m_z = 123456789 - s;
дает хорошее распределение первых значений с разными начальными числами.
- person undefined; 18.04.2016
Написать собственный генератор псевдослучайных чисел довольно просто.
Предложение Дэйва Скотеза полезно, но, как указывали другие, оно не совсем равномерно распределено.
Однако это не из-за целочисленных аргументов греха. Это просто из-за диапазона греха, который представляет собой одномерную проекцию круга. Если бы вместо этого вы взяли угол круга, он был бы однородным.
Поэтому вместо sin (x) используйте arg (exp (i * x)) / (2 * PI).
Если вам не нравится линейный порядок, немного смешайте его с xor. Фактический фактор тоже не имеет большого значения.
Для генерации n псевдослучайных чисел можно использовать код:
function psora(k, n) {
var r = Math.PI * (k ^ n)
return r - Math.floor(r)
}
n = 42; for(k = 0; k < n; k++) console.log(psora(k, n))
Также обратите внимание, что вы не можете использовать псевдослучайные последовательности, когда требуется настоящая энтропия.
Многие люди, которым в наши дни нужен генератор случайных чисел с возможностью заполнения в Javascript, используют модуль seedrandom Дэвида Бау.
Math.random
нет, но запущенная библиотека решает эту проблему. Он имеет почти все дистрибутивы, которые вы можете себе представить, и поддерживает генерацию случайных чисел с начальным значением. Пример:
ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)
Вот принятая версия хэша Jenkins, заимствованная из здесь
export function createDeterministicRandom(): () => number {
let seed = 0x2F6E2B1;
return function() {
// Robert Jenkins’ 32 bit integer hash function
seed = ((seed + 0x7ED55D16) + (seed << 12)) & 0xFFFFFFFF;
seed = ((seed ^ 0xC761C23C) ^ (seed >>> 19)) & 0xFFFFFFFF;
seed = ((seed + 0x165667B1) + (seed << 5)) & 0xFFFFFFFF;
seed = ((seed + 0xD3A2646C) ^ (seed << 9)) & 0xFFFFFFFF;
seed = ((seed + 0xFD7046C5) + (seed << 3)) & 0xFFFFFFFF;
seed = ((seed ^ 0xB55A4F09) ^ (seed >>> 16)) & 0xFFFFFFFF;
return (seed & 0xFFFFFFF) / 0x10000000;
};
}
Вы можете использовать это так:
const deterministicRandom = createDeterministicRandom()
deterministicRandom()
// => 0.9872818551957607
deterministicRandom()
// => 0.34880331158638
Большинство ответов здесь дают необъективные результаты. Итак, вот протестированная функция, основанная на библиотеке seedrandom из github:
!function(f,a,c){var s,l=256,p="random",d=c.pow(l,6),g=c.pow(2,52),y=2*g,h=l-1;function n(n,t,r){function e(){for(var n=u.g(6),t=d,r=0;n<g;)n=(n+r)*l,t*=l,r=u.g(1);for(;y<=n;)n/=2,t/=2,r>>>=1;return(n+r)/t}var o=[],i=j(function n(t,r){var e,o=[],i=typeof t;if(r&&"object"==i)for(e in t)try{o.push(n(t[e],r-1))}catch(n){}return o.length?o:"string"==i?t:t+"\0"}((t=1==t?{entropy:!0}:t||{}).entropy?[n,S(a)]:null==n?function(){try{var n;return s&&(n=s.randomBytes)?n=n(l):(n=new Uint8Array(l),(f.crypto||f.msCrypto).getRandomValues(n)),S(n)}catch(n){var t=f.navigator,r=t&&t.plugins;return[+new Date,f,r,f.screen,S(a)]}}():n,3),o),u=new m(o);return e.int32=function(){return 0|u.g(4)},e.quick=function(){return u.g(4)/4294967296},e.double=e,j(S(u.S),a),(t.pass||r||function(n,t,r,e){return e&&(e.S&&v(e,u),n.state=function(){return v(u,{})}),r?(c[p]=n,t):n})(e,i,"global"in t?t.global:this==c,t.state)}function m(n){var t,r=n.length,u=this,e=0,o=u.i=u.j=0,i=u.S=[];for(r||(n=[r++]);e<l;)i[e]=e++;for(e=0;e<l;e++)i[e]=i[o=h&o+n[e%r]+(t=i[e])],i[o]=t;(u.g=function(n){for(var t,r=0,e=u.i,o=u.j,i=u.S;n--;)t=i[e=h&e+1],r=r*l+i[h&(i[e]=i[o=h&o+t])+(i[o]=t)];return u.i=e,u.j=o,r})(l)}function v(n,t){return t.i=n.i,t.j=n.j,t.S=n.S.slice(),t}function j(n,t){for(var r,e=n+"",o=0;o<e.length;)t[h&o]=h&(r^=19*t[h&o])+e.charCodeAt(o++);return S(t)}function S(n){return String.fromCharCode.apply(0,n)}if(j(c.random(),a),"object"==typeof module&&module.exports){module.exports=n;try{s=require("crypto")}catch(n){}}else"function"==typeof define&&define.amd?define(function(){return n}):c["seed"+p]=n}("undefined"!=typeof self?self:this,[],Math);
function randIntWithSeed(seed, max=1) {
/* returns a random number between [0,max] including zero and max
seed can be either string or integer */
return Math.round(new Math.seedrandom('seed' + seed)()) * max
}
проверить истинную случайность этого кода: https://es6console.com/kkjkgur2/
Я написал функцию, которая возвращает начальное случайное число, она использует Math.sin для получения длинного случайного числа и использует начальное число для выбора из него чисел.
Использовать :
seedRandom("k9]:2@", 15)
он вернет ваше начальное число, первый параметр - любое строковое значение; ваше семя. второй параметр - сколько цифр вернется.
function seedRandom(inputSeed, lengthOfNumber){
var output = "";
var seed = inputSeed.toString();
var newSeed = 0;
var characterArray = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','y','x','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','U','R','S','T','U','V','W','X','Y','Z','!','@','#','$','%','^','&','*','(',')',' ','[','{',']','}','|',';',':',"'",',','<','.','>','/','?','`','~','-','_','=','+'];
var longNum = "";
var counter = 0;
var accumulator = 0;
for(var i = 0; i < seed.length; i++){
var a = seed.length - (i+1);
for(var x = 0; x < characterArray.length; x++){
var tempX = x.toString();
var lastDigit = tempX.charAt(tempX.length-1);
var xOutput = parseInt(lastDigit);
addToSeed(characterArray[x], xOutput, a, i);
}
}
function addToSeed(character, value, a, i){
if(seed.charAt(i) === character){newSeed = newSeed + value * Math.pow(10, a)}
}
newSeed = newSeed.toString();
var copy = newSeed;
for(var i=0; i<lengthOfNumber*9; i++){
newSeed = newSeed + copy;
var x = Math.sin(20982+(i)) * 10000;
var y = Math.floor((x - Math.floor(x))*10);
longNum = longNum + y.toString()
}
for(var i=0; i<lengthOfNumber; i++){
output = output + longNum.charAt(accumulator);
counter++;
accumulator = accumulator + parseInt(newSeed.charAt(counter));
}
return(output)
}
В PHP есть функция srand(seed)
, которая генерирует фиксированное случайное значение для определенного начального числа. Но в JS такой встроенной функции нет.
Однако мы можем написать простую и короткую функцию.
Шаг 1: выберите какое-нибудь семя (исправленное число). var seed = 100;
Число должно быть положительным целым числом больше 1, дальнейшее объяснение см. в шаге 2.
Шаг 2. Выполните функцию Math.sin () для Seed, она даст значение sin < / em> этого числа. Сохраните это значение в переменной x.
var x;
x = Math.sin(seed); // Will Return Fractional Value between -1 & 1 (ex. 0.4059..)
Метод sin () возвращает дробное значение от -1 до 1.
И нам не нужно отрицательное значение, поэтому на первом шаге выберите число больше 1.
Шаг 3. Возвращаемое значение - это дробное значение от -1 до 1.
Итак, умножьте это значение на 10, чтобы оно больше 1.
x = x * 10; // 10 for Single Digit Number
Шаг 4. Умножьте значение на 10, чтобы получить дополнительные цифры.
x = x * 10; // Will Give value between 10 and 99 OR
x = x * 100; // Will Give value between 100 and 999
Умножьте в соответствии с требованиями цифр.
Результат будет в десятичном формате.
Шаг 5. Удалите значение после десятичной точки с помощью метода Math's Round (Math.round ()).
x = Math.round(x); // This will give Integer Value.
Шаг 6. Превратите отрицательные значения в положительные (если есть) с помощью метода Math.abs
x = Math.abs(x); // Convert Negative Values into Positive(if any)
Конец объяснения.
Окончательный код
var seed = 111; // Any Number greater than 1
var digit = 10 // 1 => single digit, 10 => 2 Digits, 100 => 3 Digits and so. (Multiple of 10)
var x; // Initialize the Value to store the result
x = Math.sin(seed); // Perform Mathematical Sin Method on Seed.
x = x * 10; // Convert that number into integer
x = x * digit; // Number of Digits to be included
x = Math.round(x); // Remove Decimals
x = Math.abs(x); // Convert Negative Number into Positive
Чистый и оптимизированный функциональный код
function random_seed(seed, digit = 1) {
var x = Math.abs(Math.round(Math.sin(seed++) * 10 * digit));
return x;
}
Затем вызовите эту функцию, используя random_seed(any_number, number_of_digits)
any_number должно быть больше 1.
number_of_digits - необязательный параметр и если ничего не прошло, вернется 1 цифра.
random_seed(555); // 1 Digit
random_seed(234, 1); // 1 Digit
random_seed(7895656, 1000); // 4 Digit
Простой подход для фиксированных семян:
function fixedrandom(p){
const seed = 43758.5453123;
return (Math.abs(Math.sin(p)) * seed)%1;
}
Для числа от 0 до 100.
Number.parseInt(Math.floor(Math.random() * 100))
Math.random
таким образом, чтобы всякий раз, когда Math.random
был заполнен одним и тем же семенем, он давал одну и ту же последовательную серию случайных чисел. Этот вопрос, по сути, не касается фактического использования / демонстрации Math.random
.
- person Jack Giffin; 13.04.2018