Генератор випадкових чисел JavaScript насіння


150

Функція JavaScript Math.random()повертає випадкову величину між 0 і 1, автоматично висівається на основі поточного часу (подібно до Java, я вважаю). Однак я не думаю, що немає способу встановити для вас своє насіння.

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


1
Примітка. Для того, щоб це питання було коротким та цілеспрямованим, я розділив код, який був у цьому питанні, на відповідь Вікі спільноти нижче.
Ільмарі Каронен

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

Відповіді:


124

Одним із варіантів є http://davidbau.com/seedrandom, який є замінним замінником Math.random () на основі RC4 на основі RC4 із приємними властивостями.


18
Насіннєвий випадок Девіда Бау з тих пір став досить популярним, що він підтримує його тут, на Github . Соромно, що ECMAScript вже так довго є такою задньою сценою, що подібні речі не входять до мови. Серйозно, без насіння !!!
Їжте у Джоеса

2
@EatatJoes, Це і сором, і слава JS, що це потрібно і можливо. Досить класно, що ви можете включити один файл і отримати назад сумісні зміни, внесені до об'єкта Math. Непогано 10 днів роботи, Брендан Ейх.
Бруно Броноський

2
Для всіх, хто шукає npm сторінку цього проекту: npmjs.com/package/seedrandom
Kip

27

Якщо вам не потрібна можливість висіву, просто використовуйте Math.random()та будуйте допоміжні функції навколо неї (напр. randRange(start, end)).

Я не впевнений, який RNG ви використовуєте, але найкраще це знати та документувати, щоб ви знали його характеристики та обмеження.

Як сказав Старкий, Мерсенн Твістер - це хороший PRNG, але його непросто здійснити. Якщо ви хочете зробити це самостійно, спробуйте здійснити LCG - це дуже просто, має пристойні якості випадковості (не так добре, як Mersenne Twister), і ви можете використовувати деякі популярні константи.

РЕДАКТУВАННЯ: розгляньте чудові варіанти цієї відповіді для коротких реалізацій RNG, що насідаються з посиланням, включаючи варіант LCG.

function RNG(seed) {
  // LCG using GCC's constants
  this.m = 0x80000000; // 2**31;
  this.a = 1103515245;
  this.c = 12345;

  this.state = seed ? seed : Math.floor(Math.random() * (this.m - 1));
}
RNG.prototype.nextInt = function() {
  this.state = (this.a * this.state + this.c) % this.m;
  return this.state;
}
RNG.prototype.nextFloat = function() {
  // returns in range [0,1]
  return this.nextInt() / (this.m - 1);
}
RNG.prototype.nextRange = function(start, end) {
  // returns in range [start, end): including start, excluding end
  // can't modulu nextInt because of weak randomness in lower bits
  var rangeSize = end - start;
  var randomUnder1 = this.nextInt() / this.m;
  return start + Math.floor(randomUnder1 * rangeSize);
}
RNG.prototype.choice = function(array) {
  return array[this.nextRange(0, array.length)];
}

var rng = new RNG(20);
for (var i = 0; i < 10; i++)
  console.log(rng.nextRange(10, 50));

var digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
for (var i = 0; i < 10; i++)
  console.log(rng.choice(digits));


2
Чи не повинен модуль бути 2 ^ 31? Я читаю цей алгоритм із вікі .
Трантор Лю

3
Просто, щоб ви усвідомлювали, це не "правильно" в тому сенсі, що не виводить те, що диктує математика. Іншими словами, мова, яка може обробити велику кількість, матиме різний результат. JS задихається від великої кількості і відбиває точність (зрештою, вони плавають).
DDS

4
-1 Ця реалізація LCG скидає межу для точних цілих чисел у JavaScript, оскільки this.a * this.stateце, ймовірно, призведе до числа, що перевищує 2 ^ 53. Результатом цього є обмежений діапазон випуску продукції, а для деяких насіння можливий дуже короткий період. Далі загалом, використовуючи потужність двох для отримання mкількох досить очевидних зразків, коли ви швидше використовуєте модульну операцію, а не просте усікання, все одно немає причин не використовувати прайм.
aaaaaaaaaaaa

22

Якщо ви хочете мати можливість вказати насіння, вам просто потрібно замінити виклики на getSeconds()та getMinutes(). Ви можете передати int і використовувати половину його mod 60 для значення секунди, а другу половину модуль 60, щоб дати вам іншу частину.

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

Якщо ви хочете скористатися кращим RNG, спробуйте Mersenne Twister . Це добре перевірений і досить надійний РНГ з величезною орбітою і чудовими характеристиками.

EDIT: Я дійсно повинен бути правильним і називати це генератором псевдо випадкових чисел або PRNG.

"Той, хто використовує арифметичні методи для отримання випадкових чисел, перебуває у гріху".
                                                                                                                                                          --- Джон фон Нойман


1
Посилання на реалізацію JS Mersenne Twister: math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/JAVASCRIPT/…
orip

1
@orip У вас є джерело для 3600 початкових станів? Mersenne Twister висівають на 32-бітне число, тому PRNG має мати 4 мільярди початкових станів - лише якщо початкове насіння є справді випадковим.
Тобіас П.

2
@TobiasP. Я мав на увазі пропозицію вивести насіння з комбінацією getSeconds () та getMinutes (), 60 * 60 == 3600 можливих початкових станів. Я не мав на увазі Мерсенн Твістер.
orip

3
@orip Добре, було не ясно. Ви брали участь про Мерсенн Твістер і в наступному реченні про вроджені стани;)
Тобіас П.

2
Запитуючий питання не згадує, що їм потрібне "належне" генерування випадкових чисел для будь-якого виду криптографічно чутливих програм. Хоча вся відповідь відповідає дійсності, лише перший пункт справді має відношення до поставленого питання. Можливо, додайте фрагмент коду запропонованого рішення.
В. Рубінетті


8

Код, який ви перерахували, виглядає як Lehmer RNG . Якщо це так, то 2147483647найбільше 32-бітове підписане ціле число, 2147483647є найбільшим 32-розрядним простим і48271 є множником повного періоду, який використовується для генерації чисел.

Якщо це правда, ви можете змінити, RandomNumberGeneratorщоб взяти додатковий параметр seed, а потім встановити this.seedзначення seed; але вам доведеться бути обережними, щоб переконатися, що насіння призведе до гарного розподілу випадкових чисел (Лемер може бути таким дивним) - але більшість насіння буде добре.


7

Далі - PRNG, який може годуватися насінням на замовлення. Виклик SeedRandomповерне випадкову функцію генератора.SeedRandomможна викликати без аргументів, щоб вивести повернуту випадкову функцію на поточний час, або її можна викликати з 1 або 2 невід'ємними інтерстами як аргументи для того, щоб посіяти її з цими цілими числами. Завдяки точності з поплавковою точкою висівання лише з 1 значенням дозволить генератору запустити лише один з 2 ^ 53 різних станів.

Функція, що повертається випадковим генератором, приймає 1 цілий аргумент з іменем limit, обмеження має бути в діапазоні від 1 до 4294965886, функція поверне число в діапазоні від 0 до межі-1.

function SeedRandom(state1,state2){
    var mod1=4294967087
    var mul1=65539
    var mod2=4294965887
    var mul2=65537
    if(typeof state1!="number"){
        state1=+new Date()
    }
    if(typeof state2!="number"){
        state2=state1
    }
    state1=state1%(mod1-1)+1
    state2=state2%(mod2-1)+1
    function random(limit){
        state1=(state1*mul1)%mod1
        state2=(state2*mul2)%mod2
        if(state1<limit && state2<limit && state1<mod1%limit && state2<mod2%limit){
            return random(limit)
        }
        return (state1+state2)%limit
    }
    return random
}

Приклад використання:

var generator1=SeedRandom() //Seed with current time
var randomVariable=generator1(7) //Generate one of the numbers [0,1,2,3,4,5,6]
var generator2=SeedRandom(42) //Seed with a specific seed
var fixedVariable=generator2(7) //First value of this generator will always be
                                //1 because of the specific seed.

Цей генератор має такі властивості:

  • Він має приблизно 2 ^ 64 різних можливих внутрішніх станів.
  • Він має період приблизно 2 ^ 63, набагато більше, ніж кому-небудь реально знадобиться в програмі JavaScript.
  • Зважаючи на те, що modзначення є простими розмірами, у виході немає простого шаблону, незалежно від обраної межі. Це на відміну від деяких простіших PRNG, які демонструють деякі досить систематичні зразки.
  • Він відкидає деякі результати, щоб отримати ідеальне розповсюдження незалежно від меж.
  • Він відносно повільний, працює на моїй машині близько 10 000 000 разів за секунду.

2
Чому це створює схему? for (var i = 0; i < 400; i++) { console.log("input: (" + i * 245 + ", " + i * 553 + ") | output: " + SeedRandom(i * 245, i * 553)(20)); }
Тимофій Канський

@TimothyKanski Тому що ви неправильно використовуєте. Я не експерт, але це відбувається, тому що ви ініціалізуєте генератор на кожній ітерації, бачачи лише його перше значення на основі насіння, а НЕ повторюючи наступні номери генератора. Я вірю, що це відбудеться в будь-якому PRNG, який не перемелює насіння через визначений інтервал.
bryc

5

Якщо ви програмуєте на Typescript, я адаптував реалізацію Mersenne Twister, яка була введена у відповідь Крістофа Хенкельмана на цю нитку як клас машинопису:

/**
 * copied almost directly from Mersenne Twister implementation found in https://gist.github.com/banksean/300494
 * all rights reserved to him.
 */
export class Random {
    static N = 624;
    static M = 397;
    static MATRIX_A = 0x9908b0df;
    /* constant vector a */
    static UPPER_MASK = 0x80000000;
    /* most significant w-r bits */
    static LOWER_MASK = 0x7fffffff;
    /* least significant r bits */

    mt = new Array(Random.N);
    /* the array for the state vector */
    mti = Random.N + 1;
    /* mti==N+1 means mt[N] is not initialized */

    constructor(seed:number = null) {
        if (seed == null) {
            seed = new Date().getTime();
        }

        this.init_genrand(seed);
    }

    private init_genrand(s:number) {
        this.mt[0] = s >>> 0;
        for (this.mti = 1; this.mti < Random.N; this.mti++) {
            var s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30);
            this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
                + this.mti;
            /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
            /* In the previous versions, MSBs of the seed affect   */
            /* only MSBs of the array mt[].                        */
            /* 2002/01/09 modified by Makoto Matsumoto             */
            this.mt[this.mti] >>>= 0;
            /* for >32 bit machines */
        }
    }

    /**
     * generates a random number on [0,0xffffffff]-interval
     * @private
     */
    private _nextInt32():number {
        var y:number;
        var mag01 = new Array(0x0, Random.MATRIX_A);
        /* mag01[x] = x * MATRIX_A  for x=0,1 */

        if (this.mti >= Random.N) { /* generate N words at one time */
            var kk:number;

            if (this.mti == Random.N + 1)   /* if init_genrand() has not been called, */
                this.init_genrand(5489);
            /* a default initial seed is used */

            for (kk = 0; kk < Random.N - Random.M; kk++) {
                y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
                this.mt[kk] = this.mt[kk + Random.M] ^ (y >>> 1) ^ mag01[y & 0x1];
            }
            for (; kk < Random.N - 1; kk++) {
                y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
                this.mt[kk] = this.mt[kk + (Random.M - Random.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
            }
            y = (this.mt[Random.N - 1] & Random.UPPER_MASK) | (this.mt[0] & Random.LOWER_MASK);
            this.mt[Random.N - 1] = this.mt[Random.M - 1] ^ (y >>> 1) ^ mag01[y & 0x1];

            this.mti = 0;
        }

        y = this.mt[this.mti++];

        /* Tempering */
        y ^= (y >>> 11);
        y ^= (y << 7) & 0x9d2c5680;
        y ^= (y << 15) & 0xefc60000;
        y ^= (y >>> 18);

        return y >>> 0;
    }

    /**
     * generates an int32 pseudo random number
     * @param range: an optional [from, to] range, if not specified the result will be in range [0,0xffffffff]
     * @return {number}
     */
    nextInt32(range:[number, number] = null):number {
        var result = this._nextInt32();
        if (range == null) {
            return result;
        }

        return (result % (range[1] - range[0])) + range[0];
    }

    /**
     * generates a random number on [0,0x7fffffff]-interval
     */
    nextInt31():number {
        return (this._nextInt32() >>> 1);
    }

    /**
     * generates a random number on [0,1]-real-interval
     */
    nextNumber():number {
        return this._nextInt32() * (1.0 / 4294967295.0);
    }

    /**
     * generates a random number on [0,1) with 53-bit resolution
     */
    nextNumber53():number {
        var a = this._nextInt32() >>> 5, b = this._nextInt32() >>> 6;
        return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
    }
}

Ви можете використовувати його наступним чином:

var random = new Random(132);
random.nextInt32(); //return a pseudo random int32 number
random.nextInt32([10,20]); //return a pseudo random int in range [10,20]
random.nextNumber(); //return a a pseudo random number in range [0,1]

перевірте джерело на наявність інших методів.


0

Примітка. Цей код спочатку був включений у вищезазначене питання. Для того, щоб питання було коротким і зосередженим, я перемістив його до цієї відповіді Wiki Wiki.

Я виявив, що цей код розхитується, і, здається, він працює добре, щоб отримати випадкове число, а потім використовувати насіння згодом, але я не зовсім впевнений, як працює логіка (наприклад, звідки прийшли номери 2345678901, 48271 та 2147483647).

function nextRandomNumber(){
  var hi = this.seed / this.Q;
  var lo = this.seed % this.Q;
  var test = this.A * lo - this.R * hi;
  if(test > 0){
    this.seed = test;
  } else {
    this.seed = test + this.M;
  }
  return (this.seed * this.oneOverM);
}

function RandomNumberGenerator(){
  var d = new Date();
  this.seed = 2345678901 + (d.getSeconds() * 0xFFFFFF) + (d.getMinutes() * 0xFFFF);
  this.A = 48271;
  this.M = 2147483647;
  this.Q = this.M / this.A;
  this.R = this.M % this.A;
  this.oneOverM = 1.0 / this.M;
  this.next = nextRandomNumber;
  return this;
}

function createRandomNumber(Min, Max){
  var rand = new RandomNumberGenerator();
  return Math.round((Max-Min) * rand.next() + Min);
}

//Thus I can now do:
var letters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
var numbers = ['1','2','3','4','5','6','7','8','9','10'];
var colors = ['red','orange','yellow','green','blue','indigo','violet'];
var first = letters[createRandomNumber(0, letters.length)];
var second = numbers[createRandomNumber(0, numbers.length)];
var third = colors[createRandomNumber(0, colors.length)];

alert("Today's show was brought to you by the letter: " + first + ", the number " + second + ", and the color " + third + "!");

/*
  If I could pass my own seed into the createRandomNumber(min, max, seed);
  function then I could reproduce a random output later if desired.
*/

3
Нічого собі, функції RandomNumberGeneratorі nextRandomNumberнасправді відносяться до 1996 року. Це повинен бути Lehmer / LCG RNG. Він використовує деякі розумні математики для виконання арифметики по модулю за 32-бітовими цілими числами, які в іншому випадку будуть занадто малі, щоб містити деякі проміжні значення. Справа в тому, що JavaScript не реалізує 32-бітові цілі числа, а швидше 64-бітові плаває, і оскільки поділ не є цілим поділом, як цей код передбачає, результат не є генератором Лемера. Це дає певні результати, які здаються випадковими, але гарантії генератора Лемера не застосовуються.
aaaaaaaaaaaa

1
Ця createRandomNumberфункція є пізнішим доповненням, вона робить все набагато неправильним, особливо, вона створює новий RNG кожен раз, коли він викликається, а це означає, що виклики в швидкій послідовності будуть використовувати один і той же плавець. У даному коді практично неможливо 'a'з'єднатись з чим-небудь, крім '1'і 'red'.
aaaaaaaaaaaa

-2

Гаразд, ось таке рішення я вирішив.

Спочатку ви створюєте значення насіння за допомогою функції "newseed ()". Потім ви передаєте значення насіння у функцію "srandom ()". Нарешті, функція "srandom ()" повертає псевдовипадкове значення між 0 і 1.

Найважливішим бітом є те, що значення насіння зберігається всередині масиву. Якби це було просто ціле чи float, значення перезаписується щоразу, коли функція викликалася, оскільки значення цілих чисел, плавців, рядків і так далі зберігаються безпосередньо в стеку проти просто покажчиків, як у випадку масивів та інші об’єкти. Таким чином, цінність насіння може залишатися стійкою.

Нарешті, можна визначити функцію "srandom ()" таким чином, що це метод об'єкта "Математика", але я залишаю це розібратися. ;)

Удачі!

JavaScript:

// Global variables used for the seeded random functions, below.
var seedobja = 1103515245
var seedobjc = 12345
var seedobjm = 4294967295 //0x100000000

// Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
{
    return [seednum]
}

// Works like Math.random(), except you provide your own seed as the first argument.
function srandom(seedobj)
{
    seedobj[0] = (seedobj[0] * seedobja + seedobjc) % seedobjm
    return seedobj[0] / (seedobjm - 1)
}

// Store some test values in variables.
var my_seed_value = newseed(230951)
var my_random_value_1 = srandom(my_seed_value)
var my_random_value_2 = srandom(my_seed_value)
var my_random_value_3 = srandom(my_seed_value)

// Print the values to console. Replace "WScript.Echo()" with "alert()" if inside a Web browser.
WScript.Echo(my_random_value_1)
WScript.Echo(my_random_value_2)
WScript.Echo(my_random_value_3)

Lua 4 (моє особисте цільове середовище):

-- Global variables used for the seeded random functions, below.
seedobja = 1103515.245
seedobjc = 12345
seedobjm = 4294967.295 --0x100000000

-- Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
    return {seednum}
end

-- Works like random(), except you provide your own seed as the first argument.
function srandom(seedobj)
    seedobj[1] = mod(seedobj[1] * seedobja + seedobjc, seedobjm)
    return seedobj[1] / (seedobjm - 1)
end

-- Store some test values in variables.
my_seed_value = newseed(230951)
my_random_value_1 = srandom(my_seed_value)
my_random_value_2 = srandom(my_seed_value)
my_random_value_3 = srandom(my_seed_value)

-- Print the values to console.
print(my_random_value_1)
print(my_random_value_2)
print(my_random_value_3)

PS - Я ще не такий знайомий із Stack Overflow, але чому це не повідомлення в хронологічному порядку?
posfan12

Привіт @ posfan12 - відповіді на запитання, як правило, перераховуються в порядку "нагородження" таким чином, що "крем піднімається до вершини". Однак для забезпечення справедливого перегляду відповідей з однаковим балом вони відображаються у випадковому порядку. Оскільки це було моїм питанням спочатку ;-) Я точно буду впевнений, що незабаром це перевірять. Якщо я (або інші) вважаю цю відповідь корисною, ми її схвалимо, і якщо я вважаю, що це "правильна" відповідь, ви побачите також зелену галочку, додану до цієї відповіді. - Ласкаво просимо до StackOverflow!
сканліф

2
-1 Ця реалізація LCG скидає межу для точних цілих чисел у JavaScript, оскільки seedobj[0] * seedobjaце, ймовірно, призведе до числа, що перевищує 2 ^ 53. Результатом цього є обмежений діапазон випуску продукції, а для деяких насіння можливий дуже короткий період.
aaaaaaaaaaaa
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.