首页 文章

在Javascript中播种随机数生成器

提问于
浏览
282

是否可以在Javascript中播种随机数生成器(Math.random)?

12 回答

  • 155

    不,它不是,但是编写自己的生成器相当容易,或者更好地使用现有的生成器 . 退房:this related question .

    另请参阅David Bau的博客more information on seeding .

  • -3

    注意:尽管(或者更确切地说,由于)简洁和明显的优雅,这种算法在随机性方面绝不是高质量的算法 . 寻找例如this answer中列出的那些更好的结果 .

    (最初改编自对另一个答案的评论中提出的一个聪明的想法 . )

    var seed = 1;
    function random() {
        var x = Math.sin(seed++) * 10000;
        return x - Math.floor(x);
    }
    

    您可以将 seed 设置为任意数字,只需避免零(或Math.PI的任意倍数) .

    在我看来,这个解决方案的优雅来自缺少任何"magic"数字(除了10000,代表你必须扔掉以避免奇怪模式的最小位数 - 请参阅值为101001000的结果) . 简洁也很好 .

    它比Math.random()稍微慢一点(比例为2或3),但我相信它与用JavaScript编写的任何其他解决方案一样快 .

  • 10

    不,但这是一个简单的伪随机生成器,Multiply-with-carry的实现我改编自Wikipedia(之后已被删除):

    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:严重的实施缺陷已得到修复

  • 124

    我在纯JavaScript中实现了许多优秀,简短,快速的可复制PRNG函数 . 所有这些都可以播种并提供高质量的数字 .

    First of all, remember to seed your PRNGs properly. 下面的大多数生成器没有内置种子设定程序,但接受一个或多个32位值来初始化PRNG的状态 . 而不是只使用低熵的"1"或"123"播种并且可能导致相关性(这不好),最好使用分布均匀的数据初始化PRNG .

    值得庆幸的是,哈希算法非常擅长从短字符串生成PRNG的种子 . 即使两个字符串相似,良好的散列函数也会产生非常不同的结果 . 这是一个基于FNV1a-Mulvey哈希的简单示例:

    function xfnv1a(str) {
        for(var i = 0, h = 2166136261 >>> 0; i < str.length; i++)
            h = Math.imul(h ^ str.charCodeAt(i), 16777619);
        return function() {
            h += h << 13; h ^= h >>> 7;
            h += h << 3;  h ^= h >>> 17;
            return (h += h << 5) >>> 0;
        }
    }
    

    此功能使用Bret Mulvey的modified FNV1a 32位散列函数 . 对返回函数的每次后续调用都会生成一个新的"random"哈希值,以用作PRNG中的种子 .

    以下是您可以使用它的方法:

    // Create a xfnv1a state:
    var seed = xfnv1a("apples");
    // Output four 32-bit hashes to provide the seed for sfc32.
    var rand = sfc32(seed(), seed(), seed(), seed());
    
    // Or: output one 32-bit hash to provide the seed for mulberry32.
    var rand = mulberry32(seed());
    
    // Obtain sequential random numbers like so:
    rand();
    rand();
    

    这当然是功能性的JS,但它可以被客观化 .

    向货物(发电机)前进 .


    sfc32

    这个gem来自PractRand随机数测试套件,它可以毫无问题地通过 . PractRand据称甚至比TestU01更严格 . 它在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位的状态和232的完整周期 . 如果你只想用一个32位整数播种而不关心birthday problem,这是理想的选择 . 与sfc32 / xoshiro128 **中的340亿人相比,有43亿州 .

    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年5月, 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的最新版本,他也使用了谷歌Chrome中的xorshift128和xoroshiro . 它是少数现代32位PRNG之一 . xoroshiro64**也很有希望,但只有64位状态,并且很大程度上被xoshiro取代 .

    使用如下:

    var rand = xoshiro128ss( seed(), seed(), seed(), seed() );
    rand(); // 0.7410467516165227
    

    作者声称它很好地通过了随机性测试(albeit with caveats) . 其他研究人员指出,在BigCrush(特别是LinearComp和BinaryRank)中进行了一些测试失败 . 但实际上并不重要,特别是如果将32位值转换为0-1之间的浮点数,就像这些PRNG一样 . 但是,如果您以笨拙的方式依赖低位,则可能会导致问题 .

    JSF

    这是Bob Jenkins(2007)的JSF或 smallprng ,是制作ISAACSpookyHash的人 . 它在PractRand上测试performs well应该非常快 . 假设平均周期长度为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) / 4294967295; // 2^32-1
        }
        seed >>>= 0;
        var s = [0xf1ea5eed, seed, seed, seed];
        for(var i=0;i<20;i++) jsf();
        return jsf;
    }
    

    此版本不需要单独的种子功能 . 但结果是,只有32位可以播种,并且该版本运行预先运行jsf()20次以分散初始状态,这可能是昂贵的 .

    像这样使用:

    var rand = JSF( any_32bit_seed );
    rand(); // 0.098275076597929
    

    如果需要,可以直接初始化整个128位状态并删除for循环 . 我决定保留原始结构,因为作者验证了给定配置中每个可能的32位种子的周期长度 .

    Lehmer LCG

    这个仅用于提供其他答案中提到的选项的更好替代方案,例如 Math.sinMath.PI 方法,这些方法在不同平台上不太一致或可靠 . 这个LCG实现是 extremely fast 但只有一个31位的状态,并且未能通过前面提到的生成器通过飞行颜色的一些统计测试 .

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

    虽然这是一个单行 - 这很好:) . 这是Park–Miller in 1988 & 1993提出的最小标准RNG,并在C11中实现为 minstd_rand . 请记住,状态和周期仅为31位(31位给出20亿个可能状态,32位给出两倍) . 这是其他人试图取代的PRNG类型 .

    除非你真的需要速度并且不关心随机性质量(无论是什么是随机的?)或31位状态/周期大小,否则它会使用它 . 非常适合游戏果酱或演示或其他东西 .

    使用如下:

    var rand = LCG( any_31bit_seed );
    rand(); // 0.45899124443531036
    

    似乎还有其他乘法器可以使您获得完整的32位状态 . 我不知道这些是否比Park-Miller更好/更差,但在这里它们是完整的 .

    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:1997年4月30日的不同尺寸和良好晶格结构的线性同余发电机表 .

  • -1

    AnttiSykäri的算法很简单 . 当你调用Math.seed(s)时,我最初做了一个替换Javascript的Math.random的变体,但是后来Jason评论说返回函数会更好:

    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不具备的另一项功能:多个独立的随机生成器 . 如果您希望同时运行多个可重复的模拟,那么这一点尤为重要 .

  • 24

    请参阅Pierre L'Ecuyer的工作,可追溯到20世纪80年代末和90年代初 . 还有其他人 . 如果您不是专家,自己创建一个(伪)随机数生成器是非常危险的,因为很可能结果不是统计随机的或者是一个小周期 . 皮埃尔(和其他人)已经将一些易于实现的好(伪)随机数生成器组合在一起 . 我使用他的一个LFSR发电机 .

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

    菲尔特洛伊

  • -5

    编写自己的伪随机生成器非常简单 .

    Dave Scotese的建议很有用,但正如其他人所指出的那样,它的分布并不均匀 .

    但是,这不是因为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))
    

    另请注意,当需要真正的熵时,不能使用伪随机序列 .

  • 3

    如今,许多在Javascript中需要可播种随机数生成器的人正在使用David Bau's seedrandom module .

  • 3

    结合以前的一些答案,这是您正在寻找的可种子随机函数:

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

    我编写了一个返回种子随机数的函数,它使用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)
          }
    
  • 28

    固定种子的简单方法:

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

    对于0到100之间的数字 .

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

相关问题