Посів генератора випадкових чисел у Javascript


372

Чи можна запустити генератор випадкових чисел (Math.random) у Javascript?


незрозуміло, чи хочете ви висаджувати його так, щоб ви отримували однакові результати неодноразово для різних тестових циклів, чи хочете викласти їх на «щось унікальне» для користувача для кращої випадковості між використанням.
simbo1905

2
Ні, на жаль це неможливо. jsrand - це невелика бібліотека, про яку я писав, коли мені потрібен був PRNG, який можна використати. Є й інші, більш складні бібліотеки, в яких можна знайти Google.
Доменіко Де Феліче

4
Додаючи до запитання: як, можливо, пропонувати ПРНГ без засобів її посіву ?? Чи є якась вагома причина для цього?
Алан

Дивіться також stackoverflow.com/questions/424292
Palimondo

Відповіді:



159

ПРИМІТКА: Незважаючи на (вірніше, через) лаконічність та очевидну елегантність, цей алгоритм аж ніяк не є якісним з точки зору випадковості. Для кращих результатів шукайте, наприклад, перелічених у цій відповіді .

(Спочатку адаптована з розумної ідеї, представленої в коментарі до іншої відповіді.)

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.


20
Чи є спосіб довести, що цей RNG генерує числа, рівномірно розподілені? Експериментально здається: jsfiddle.net/bhrLT
Натан Брейт

6
6 000 000 оп / секунда досить швидко, я не планую генерувати більше ~ 3 000 000 за клік. Жартую, це геніально.
AMK

59
-1, це взагалі не є рівномірним пробовідбірником - він досить упереджений щодо 0 та 1 (див. Jsfiddle.net/bhrLT/17 , для обчислення може знадобитися певний час). Послідовні значення співвідносяться - кожні 355 значень, а тим більше кожні 710, пов'язані між собою. Будь ласка, використовуйте щось більш ретельно продумане!
Спенсер Нельсон

37
Питання полягає не в створенні криптографічно захищеного генератора випадкових чисел, а в тому, що працює в javascript, корисно для швидких демонстрацій і т.д.
Джейсон Джемаат

15
Будь обережний. Math.sin () може давати різні результати на клієнті та сервері. Я використовую Meteor (використовує javascript на клієнті та сервері).
Obiwahn

145

Я реалізував ряд добрих, коротких та швидких функцій генератора псевдовипадкових чисел (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 (простий швидкий лічильник)

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

Я рекомендую це, якщо вам просто потрібен простий, але гідний PRNG і не потрібні мільярди випадкових чисел (див. Проблему з днем ​​народження ).

xoshiro128 **

Станом на травень 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 (Маленький швидкий Дженкінс)

Це 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 (він же Lehmer / Park-Miller RNG або MCG)

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 року.


5
Це дивовижна відповідь. Я впевнений, що до цього повернуся.
DavidsKanal

1
Я вважаю, що значення, які ви цитували з "Таблиць лінійних конгрурентних генераторів ..." П'єра Льокейєра, могли перевищити максимальний цілий розмір у Javascript. Максимальне насіння (2 ^ 32-1) * 741103597 ≈ 3e18, що перевищує максимальний розмір JavaScript в ≈ 9e15. Я думаю , що такі значення з книги П'єра мають великий період в межах рідних кордонів: seed = (seed * 185852 + 1) % 34359738337.
Lachmanski

1
@Lachmanski вірно, але вони пов'язані 32-бітними (і Park-Miller 31-бітними). Використання Math.imulдозволяє йому переповнюватись так, як це було б при використанні множення в С на 32-бітні цілі числа. Що ви пропонуєте, це LCG, що використовує весь спектр цілого простору JS, що, безумовно, також є цікавою областю для вивчення. :)
bryc

1
Це круто! Чи можу я просто скопіювати ваш sfc32 в програму LGPL?
user334639

4
@ blobber2 не впевнений, що ти маєш на увазі, але вихідний код звідси (з іншими): github.com/bryc/code/blob/master/jshash/PRNGs.md . більш-менш суть всередині сховища :-)
bryc

39

Ні, але ось простий псевдовипадковий генератор, реалізація адаптованого з Вікіпедії множинного перенесення (видалено з тих пір):

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: Виправлені серйозні недоліки в реалізації


3
Хтось перевіряв цю функцію на її випадковість?
Джастін

3
Це випадковий генератор з множиною несучої (MWC) з досить тривалим періодом. Адаптовано з wikipedia Генератори випадкових чисел
Michael_Scharf

10
seedФункція не скидати генератор випадкових, так як mz_zзмінна змінюється , коли random()викликається. Тому встановіть mz_z = 987654321(або будь-яке інше значення) вseed
Michael_Scharf

Коли я використовую його зі своїм випадковим генератором кольорів (HSL), він генерує лише зелений і блакитний кольори. Оригінальний випадковий генератор генерує всі кольори. Отже, це не те саме або це не працює.
Томаш Кубес

@Michael_Scharf 1) Зміна насіння m_w, а НЕ m_z. 2) І те, m_wі m_zінше змінюється на основі їх попередніх значень, так що це змінює результат.
ESL

26

Алгоритм Анті Сікарі хороший і короткий. Я спочатку вніс зміни, які замінили 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 немає: кілька незалежних випадкових генераторів. Це особливо важливо, якщо ви хочете мати кілька повторюваних симуляцій одночасно.


3
Якщо ви повернете функцію замість настройки, Math.randomяка дозволить вам мати кілька незалежних генераторів, правда?
Джейсон Гімаат

1
Будьте впевнені , щоб побачити коментарі вище про розподіл випадковістю , якщо це для вас важливо: stackoverflow.com/questions/521295 / ...
jocull

Як породжені цим викупки можуть повторюватися? Він постійно надає нові номери
SMUsamaShah

Кожен раз, коли ви Math.seed(42);це виконуєте, скидає цю функцію, тож якщо ви її var random = Math.seed(42); random(); random();отримаєте 0.70..., то 0.38.... Якщо ви скинете його var random = Math.seed(42);знову, зателефонувавши , то наступного разу, коли ви подзвоните, random()ви отримаєте 0.70...знову, а наступного разу - 0.38...знову.
ВІДПОВІСТИСтевенДжонс

1
Будь ласка, не використовуйте це. Будь ласка, знайдіть час, щоб замість randomтого, щоб перезаписати нативну функцію javascript, використовувати локальну змінну, названу замість. Перезапис Math.randomможе призвести до того, що компілятор JIST неоптимізує весь ваш код.
Джек Гіффін

11

Подивіться, будь ласка, роботи П'єра Л'Екуйєра наприкінці 1980-х та початку 1990-х. Є й інші. Створити власний (псевдо) генератор випадкових чисел самостійно, якщо ви не експерт, досить небезпечно, оскільки існує велика ймовірність того, що результати не будуть статистично випадковими, або матимуть невеликий період. П'єр (та інші) зібрали кілька хороших (псевдо) генераторів випадкових чисел, які легко здійснити. Я використовую один з його генераторів LFSR.

https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf

Філ Трой


1
Чудова відповідь, але не пов’язана з javascript :)
Микола Фоміних

3
Код для виконання робіт професора Л'Екуєра є загальнодоступним для java та легко перекладається більшістю програмістів у Javascript.
user2383235

6

Поєднуючи деякі попередні відповіді, це шукана випадкова функція, яку ви шукаєте:

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

4
Це дає дуже схожі результати на початку послідовності з різними насінням. Наприклад, Math.seed(0)()повертається 0.2322845458984375і Math.seed(1)()повертається 0.23228873685002327. Зміна як насіння, так m_wі m_zвідповідно до насіння, здається, допомагає. var m_w = 987654321 + s; var m_z = 123456789 - s;дає хороший розподіл перших значень з різними насінням.
визначено

1
@undefined описану вами проблему виправлено останньою редакцією, це була помилка в реалізації MWC.
bryc

Працює зараз добре, станом на січень 2020 року. Насіння з 0 отримайте 0,7322976540308446. Насіння з 1, 0,16818441334180534, з 2: 0,6040864314418286, з 3: 0,03998844954185188. Дякую обом!
Еврика

3

Написати власний псевдо випадковий генератор досить просто.

Пропозиція Дейва Шотландська є корисною, але, як зазначають інші, вона не зовсім рівномірно розподілена.

Однак це не через цілі аргументи гріха. Це просто через діапазон гріха, який буває одновимірною проекцією кола. Якщо б ви взяли кут кола, замість цього він був би рівномірним.

Тож замість 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))

Зауважте також, що ви не можете використовувати псевдовипадкові послідовності, коли потрібна реальна ентропія.


Я не експерт, але послідовні насіння слідують постійній схемі . Кольорові пікселі> = 0,5. Я здогадуюсь його просто ітерація над радіусом знову і знову?
bryc


1

Math.randomні, але запущена бібліотека вирішує це. Він має майже всі дистрибутиви, які ви можете уявити, і підтримує насіння випадкових чисел. Приклад:

ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)

-1

Я написав функцію, яка повертає посіяне випадкове число, воно використовує 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)
      }

1
Послідовності чисел, вироблені цим, насправді не наближають властивості послідовностей випадкових чисел. Створіть з нею 15 чисел, і отриманий рядок майже завжди починається з 7 для майже будь-якого ключа, наприклад.
Габріель


-6

Для числа від 0 до 100.

Number.parseInt(Math.floor(Math.random() * 100))

3
Питання полягає в тому, щоб висівати насіння Math.randomтаким чином, що всякий раз, коли Math.randomпосіяти одне і те ж насіння, воно дасть однакові послідовні серії випадкових чисел. Це питання, скажімо, не стосується фактичного використання / демонстрації Math.random.
Джек Гіффін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.