Заселение генератора случайных чисел в Javascript

Можно ли засеять генератор случайных чисел (Math.random) в Javascript?

Ответ 2

ПРИМЕЧАНИЕ. Несмотря на (или, скорее, из-за) лаконичность и кажущуюся элегантность, этот алгоритм ни в коем случае не является качественным с точки зрения случайности. Ищите, например, те, которые указаны в этом ответе для получения лучших результатов.

(Первоначально адаптированный из умной идеи, представленной в комментарии к другому ответу.)

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.

Ответ 3

Я реализовал ряд хороших, коротких и быстрых копируемых 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;
    }
}

Каждый последующий вызов функции возврата создает новое "случайное" 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();

Это, конечно, функциональный JS, но он может быть объективизирован.

Следует также отметить, что все это 32-разрядные генераторы, что означает, что все связано 32-разрядными операциями, имитирующими 32-разрядный код C. Это оказывается достойным компромиссом, поскольку 32-разрядные целые числа получают некоторый импульс оптимизации в современных движках JS. JS может выполнять только 32-битные побитовые операции и даже не может выполнять 64-битную математику. У JS есть ограничение в 53-битных целых числах, но даже тогда вам нужно много хитростей, чтобы эффективно их использовать. Вот почему Baagøe предположительно сверхбыстрый 53-битный генератор Alea работает медленнее, чем эти реализации.

Вперед к товарам (генераторам).


SFC32

Этот драгоценный камень происходит из набора для тестирования случайных чисел PractRand, из которого он проходит без проблем. PractRand якобы даже более строг, чем TestU01. sfc32 имеет 128-битное состояние и также очень быстр в JS (xoshiro128 ** немного быстрее, но хуже по качеству). Это наверное мой PRNG по выбору.

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

Mulberry32 также довольно быстр и имеет хорошее качество (автор утверждает, что он прошел все тесты gjrand). Я бы порекомендовал это, если вам просто нужен простой, но приличный PRNG.

Он имеет состояние 32 бита и полный период 2 32. Идеально, если вы хотите использовать только одно 32-разрядное целое число и не беспокоиться о проблеме дня рождения. В Mulberry32 имеется 4,3 миллиарда возможных состояний по сравнению с 340 ундециллионами в sfc32/xoshiro128 **.

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. Он предлагает 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;
    }
}

Этот PRNG является последним от Blackman/Vigna, который также написал PRNG xorshift128+ и xoroshiro, которые использовались в Google Chrome еще в 2015 году. Он известен как один из немногих современных PRNG с 32-битной версией. xoroshiro64 ** также является многообещающим вариантом, но имеет только 64-битное состояние и в значительной степени заменен на xoshiro.

Авторы утверждают, что он хорошо проходит тесты на случайность (хотя и с оговорками). Другие исследователи отмечают, что некоторые тесты в BigCrush не проходят (особенно LinearComp и BinaryRank). Но на практике это не должно иметь значения, особенно если 32-битное значение преобразуется в число с плавающей запятой между 0-1, как эти PRNG. Однако это может вызвать проблему, если вы полагаетесь на младшие биты.

JSF

Это JSF или "smallprng" Боба Дженкинса (2007), парня, который создал ISAAC и SpookyHash. Он хорошо работает на тестах PractRand и должен быть довольно быстрым. Предполагается, что средняя продолжительность периода составляет 2 ^ 126, но "формально не определена".

function JSF(seed) {
    function jsf() {
        var e = s[0] - (s[1]<<27 | s[1]>>>5);
         s[0] = s[1] ^ (s[2]<<17 | s[2]>>>15),
         s[1] = s[2] + s[3],
         s[2] = s[3] + e, s[3] = s[0] + e;
        return (s[3] >>> 0) / 4294967296; // 2^32
    }
    seed >>>= 0;
    var s = [0xf1ea5eed, seed, seed, seed];
    for(var i=0;i<20;i++) jsf();
    return jsf;
}

Эта версия не нуждается в отдельной функции семени. Но в результате можно посеять только 32 бита, и эта версия предварительно запускает jsf() 20 раз для разгона исходного состояния, что может быть дорогостоящим.

При необходимости можно полностью инициализировать все 128-битное состояние и удалить цикл for. Я решил оставить исходную конструкцию, потому что автор проверил длину цикла каждого возможного 32-разрядного начального числа в данной конфигурации.

LCG (он же Lehmer/Park-Miller RNG или MLCG)

Это только здесь, чтобы предоставить лучшую альтернативу опциям, упомянутым в других ответах, таких как методы Math.sin или Math.PI, которые менее согласованы или надежны на разных платформах. Эта реализация LCG чрезвычайно быстрая, но имеет только 31-битное состояние и не проходит некоторые статистические тесты, которые ранее упомянутые генераторы прошли с плавающими цветами. Это однострочник - что приятно :).

var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;

Это минимальный стандартный ГСЧ, предложенный Пак-Миллером в 1988 и 1993 годах и реализованный в С++ 11 как minstd_rand. Помните, что состояние и период только 31-битный (31 бит дает 2 миллиарда возможных состояний, 32 бита - вдвое больше). Это тот самый тип PRNG, который пытаются заменить другие.

Это сработает, но я бы не стал использовать его, если вам действительно не нужна скорость и вас не волнует качество случайности (что вообще такое случайность?) Или если вы не против 31-битного размера состояния/периода. Отлично подходит для игрового джема или демо или чего-то еще. Кроме того, LCG страдают от начальных корреляций, поэтому лучше отказаться от первого результата LCG.

Кажется, есть другие множители, которые предлагают полное 32-битное состояние. Я понятия не имею, статистически лучше или хуже, чем Парк-Миллер, но здесь они для полноты.

var LCG=s=>()=>((s=Math.imul(741103597,s))>>>0)/2**32;
var LCG=s=>()=>((s=Math.imul(1597334677,s))>>>0)/2**32;

Эти множители получены из: P. L'Ecuyer: Таблица линейных конгруэнтных генераторов разных размеров и с хорошей структурой решетки, 30 апреля 1997 г.

Ответ 4

Нет, но здесь простой псевдослучайный генератор, реализация Multiply-with-carry, которую я адаптировал из Википедии (был удален с тех пор):

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
РЕДАКТИРОВАТЬ 2: Серьезные недостатки реализации были исправлены

Ответ 5

Алгоритм Antti Sykäri хорош и короток. Я изначально сделал вариант, который заменил Javascript Math.random, когда вы вызываете 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 не имеет: несколько независимых случайных генераторов. Это особенно важно, если вы хотите, чтобы одновременно выполнялось несколько повторяющихся симуляций.

Ответ 6

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

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

Фил Трой

Ответ 7

Комбинируя некоторые из предыдущих ответов, это искомая случайная функция, которую вы ищете:

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

Ответ 8

Для написания собственного псевдослучайного генератора достаточно просто.

Предложение Дейва Скотеза полезно, но, как отмечают другие, оно не совсем равномерно распределено.

Однако это не из-за целых аргументов sin. Это просто из-за диапазона греха, который оказывается одномерной проекцией круга. Если бы вы взяли угол круга, он был бы однородным.

Поэтому вместо 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))

Также обратите внимание, что вы не можете использовать псевдослучайные последовательности, когда нужна настоящая энтропия.

Ответ 9

Многие люди, нуждающиеся в генерации генератора случайных чисел в Javascript в эти дни, используют модуль семенников David Bau.

Ответ 10

Math.random нет, но библиотека Math.random решает эту проблему. Он имеет почти все дистрибутивы, которые вы можете себе представить, и поддерживает генерацию случайных чисел. Пример:

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

Ответ 11

Я написал функцию, которая возвращает засеянное случайное число, он использует Math.sin, чтобы получить длинное случайное число, и использует начальное число для выбора чисел из этого.

Используйте:

seedRandom("k9]:[email protected]", 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)
      }

Ответ 12

Простой подход для фиксированного семени:

function fixedrandom(p){
    const seed = 43758.5453123;
    return (Math.abs(Math.sin(p)) * seed)%1;
}

Ответ 13

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

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