Чи можна запустити генератор випадкових чисел (Math.random) у Javascript?
Чи можна запустити генератор випадкових чисел (Math.random) у Javascript?
Відповіді:
Ні, це не так, але написати власний генератор досить просто, а ще краще використовувати вже існуючий. Перевірте: це пов’язане питання .
Також див. Блог Девіда Бау для отримання додаткової інформації про висівання насіння .
ПРИМІТКА: Незважаючи на (вірніше, через) лаконічність та очевидну елегантність, цей алгоритм аж ніяк не є якісним з точки зору випадковості. Для кращих результатів шукайте, наприклад, перелічених у цій відповіді .
(Спочатку адаптована з розумної ідеї, представленої в коментарі до іншої відповіді.)
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.
Я реалізував ряд добрих, коротких та швидких функцій генератора псевдовипадкових чисел (PRNG) у простому JavaScript. Всі вони можуть бути посіяні і забезпечити хорошу якість.
Перш за все, подбайте про те, щоб ініціалізувати свої PRNG належним чином. Більшість генераторів нижче не мають вбудованої процедури генерації насіння (заради простоти), але приймають одне або більше 32-бітних значень як початковий стан PRNG. Подібні насіння (наприклад, просте насіння 1 і 2) можуть спричинити кореляцію у слабших PRNG, внаслідок чого вихід має схожі властивості (наприклад, рівні, що генеруються випадковим чином, схожі). Щоб цього уникнути, найкраще практикувати ініціалізацію PRNG з добре розподіленим насінням.
На щастя, хеш-функції дуже добре створюють насіння для PRNG з коротких рядків. Хороша хеш-функція дасть дуже різні результати, навіть якщо дві струни схожі. Ось приклад, заснований на функції змішування 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-бітове значення хеш - функції , які будуть використовуватися в якості затравки в PRNG. Ось як ви можете ним скористатися:
// 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 ітерацій), щоб ретельно перемішати початковий стан. Це часто спостерігається у посилальних реалізаціях PRNG, але це обмежує кількість початкових станів.
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 . Якщо ви хочете лише сирі шматочки, просто видаліть остаточну операцію поділу.
Ще одне, що слід зазначити, є обмеження JS. Числа можуть представляти лише цілі числа до 53-розрядної роздільної здатності. А при використанні побітових операцій це зменшується до 32. Це ускладнює реалізацію алгоритмів, написаних на С або С ++, які використовують 64-бітні числа. Для перенесення 64-бітного коду потрібні збитки, які можуть різко знизити продуктивність. Тож для простоти та ефективності я розглядав лише алгоритми, які використовують 32-бітну математику, оскільки вона безпосередньо сумісна з JS.
Тепер, далі до генераторів. (Я підтримую повний список із посиланнями тут )
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;
}
}
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;
}
}
Я рекомендую це, якщо вам просто потрібен простий, але гідний PRNG і не потрібні мільярди випадкових чисел (див. Проблему з днем народження ).
Станом на травень 2018 року xoshiro128 ** є новим членом сім’ї Xorshift від Vigna / Blackman (який також написав xoroshiro, який використовується в Chrome). Це найшвидший генератор, який пропонує 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 або 'smallprng' Боб Дженкінс (2007), хлопець, який створив ISAAC та SpookyHash . Він проходить тести PractRand і повинен бути досить швидким, хоча і не таким швидким, як SFC.
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;
}
}
LCG надзвичайно швидкий і простий, але якість його випадковості настільки низька, що неправильне використання насправді може спричинити помилки у вашій програмі! Тим не менш, це значно краще, ніж деякі відповіді, що пропонують використовувати Math.sin
або Math.PI
! Це одно-лайнер хоч, що приємно :).
var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;
Ця реалізація називається мінімальним стандартним РНГ, запропонованим Парком-Міллером у 1988 та 1993 роках та реалізованим у C ++ 11 як minstd_rand
. Майте на увазі, що стан є 31-бітним (31 біт дає 2 мільярди можливих станів, 32 біт - це вдвічі більше). Це той самий тип PRNG, який інші намагаються замінити!
Він буде працювати, але я б його не використовував, якщо вам справді не потрібна швидкість і не піклується про якість випадкових випадків (що все одно є випадковим?) Чудово підходить для ігрового варення, демонстрації чи чогось іншого. РГГ страждають від кореляції насіння, тому найкраще відмовитися від першого результату ЛГЧ. І якщо ви наполягаєте на використанні LCG, додавання значення приросту може покращити результати, але це, мабуть, вправність у марності, коли існують набагато кращі варіанти.
Здається, є й інші множники, що пропонують 32-бітний стан (збільшений простір станів):
var LCG=s=>()=>(s=Math.imul(741103597,s)>>>0)/2**32;
var LCG=s=>()=>(s=Math.imul(1597334677,s)>>>0)/2**32;
Ці значення LCG походять від: P. L'Ecuyer: Таблиця лінійних конгрурентних генераторів різних розмірів та хорошої структури решітки, 30 квітня 1997 року.
seed = (seed * 185852 + 1) % 34359738337
.
Math.imul
дозволяє йому переповнюватись так, як це було б при використанні множення в С на 32-бітні цілі числа. Що ви пропонуєте, це LCG, що використовує весь спектр цілого простору JS, що, безумовно, також є цікавою областю для вивчення. :)
Ні, але ось простий псевдовипадковий генератор, реалізація адаптованого з Вікіпедії множинного перенесення (видалено з тих пір):
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;
}
EDIT: фіксована функція насіння за допомогою скидання m_z
EDIT2: Виправлені серйозні недоліки в реалізації
seed
Функція не скидати генератор випадкових, так як mz_z
змінна змінюється , коли random()
викликається. Тому встановіть mz_z = 987654321
(або будь-яке інше значення) вseed
m_w
, а НЕ m_z
. 2) І те, m_w
і m_z
інше змінюється на основі їх попередніх значень, так що це змінює результат.
Алгоритм Анті Сікарі хороший і короткий. Я спочатку вніс зміни, які замінили 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
яка дозволить вам мати кілька незалежних генераторів, правда?
Math.seed(42);
це виконуєте, скидає цю функцію, тож якщо ви її var random = Math.seed(42); random(); random();
отримаєте 0.70...
, то 0.38...
. Якщо ви скинете його var random = Math.seed(42);
знову, зателефонувавши , то наступного разу, коли ви подзвоните, random()
ви отримаєте 0.70...
знову, а наступного разу - 0.38...
знову.
random
того, щоб перезаписати нативну функцію javascript, використовувати локальну змінну, названу замість. Перезапис Math.random
може призвести до того, що компілятор JIST неоптимізує весь ваш код.
Подивіться, будь ласка, роботи П'єра Л'Екуйєра наприкінці 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;
дає хороший розподіл перших значень з різними насінням.
Написати власний псевдо випадковий генератор досить просто.
Пропозиція Дейва Шотландська є корисною, але, як зазначають інші, вона не зовсім рівномірно розподілена.
Однак це не через цілі аргументи гріха. Це просто через діапазон гріха, який буває одновимірною проекцією кола. Якщо б ви взяли кут кола, замість цього він був би рівномірним.
Тож замість 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, сьогодні використовують модуль натхнення Девіда Бау .
Math.random
ні, але запущена бібліотека вирішує це. Він має майже всі дистрибутиви, які ви можете уявити, і підтримує насіння випадкових чисел. Приклад:
ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)
Я написав функцію, яка повертає посіяне випадкове число, воно використовує 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)
}
Простий підхід для фіксованого насіння:
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
.