Math.random
是否可以在 JavaScript中播种随机数生成器( )?
16 回答
不,不可能播种Math.random()
。
我已经用纯 JavaScript 实现了许多好的、简短和快速的伪随机数生成器(PRNG) 函数。所有这些都可以播种并提供高质量的数字。
首先,注意正确初始化您的 PRNG。为简单起见,下面的生成器没有内置种子生成过程,但接受一个或多个 32 位值作为 PRNG 的初始种子状态。相似或稀疏的种子(例如 1 和 2 的简单种子)具有低熵,并且可能导致相关性或其他随机性质量问题,有时会导致输出具有相似的属性(例如随机生成的级别相似)。为避免这种情况,最佳实践是使用分布良好的高熵种子初始化 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 上的这篇文章。如果您只想要原始位,只需删除最后的除法操作。
JavaScript 数字只能表示分辨率高达 53 位的整数。而当使用按位运算时,这会减少到 32。其他语言中的现代 PRNG 通常使用 64 位运算,这在移植到 JS 时需要 shims,这会大大降低性能。这里的算法只使用 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;
}
}
你可能想知道| 0
and>>>= 0
是干什么用的。这些本质上是 32 位整数转换,用于性能优化。Number
在 JS 中基本上是浮点数,但在按位运算期间,它们会切换到 32 位整数模式。这种模式被 JS 解释器处理得更快,但是任何乘法或加法都会导致它切换回浮点数,从而导致性能下降。
桑树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 年 5 月,xoshiro128**是 Vigna 和 Blackman 的Xorshift 家族的新成员(Vigna 教授还负责为大多数底层实现提供支持的 Xorshift128+ 算法Math.random
)。它是提供 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(詹金斯的小快)
这是 Bob Jenkins (2007) 的 JSF 或“smallprng”,他还制作了ISAAC和SpookyHash。它通过了 PractRand 测试,应该相当快,虽然不如 sfc32 快。
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;
}
}
注意:尽管(或者更确切地说,因为)简洁和明显的优雅,这个算法在随机性方面绝不是一个高质量的算法。寻找例如此答案中列出的那些以获得更好的结果。
(最初改编自对另一个答案的评论中提出的一个聪明的想法。)
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 编写的解决方案一样快。
不,但这是一个简单的伪随机生成器,它是我改编自Wikipedia的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;
}
Antti Sykäri 的算法既好又短。Math.random
我最初做了一个变体,在你调用时替换了 JavaScript Math.seed(s)
,但后来 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 没有的另一个功能:多个独立的随机生成器。如果您想同时运行多个可重复的模拟,这一点尤其重要。
请参阅 Pierre L'Ecuyer 可追溯到 1980 年代末和 1990 年代初的作品。还有其他的。如果您不是专家,那么您自己创建一个(伪)随机数生成器是非常危险的,因为结果很可能不是统计上随机的,或者周期很短。Pierre(和其他人)已经组合了一些易于实现的好(伪)随机数生成器。我使用他的一台 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();
编写自己的伪随机生成器非常简单。
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))
另请注意,当需要真实熵时,您不能使用伪随机序列。
如今,许多需要 Javascript 中可播种的随机数生成器的人都在使用David Bau 的 seedrandom 模块。
这里的大多数答案都会产生有偏见的结果。所以这是一个基于来自 github 的 seedrandom 库的测试函数:
!function(f,a,c){var s,l=256,p="random",d=c.pow(l,6),g=c.pow(2,52),y=2*g,h=l-1;function n(n,t,r){function e(){for(var n=u.g(6),t=d,r=0;n<g;)n=(n+r)*l,t*=l,r=u.g(1);for(;y<=n;)n/=2,t/=2,r>>>=1;return(n+r)/t}var o=[],i=j(function n(t,r){var e,o=[],i=typeof t;if(r&&"object"==i)for(e in t)try{o.push(n(t[e],r-1))}catch(n){}return o.length?o:"string"==i?t:t+"\0"}((t=1==t?{entropy:!0}:t||{}).entropy?[n,S(a)]:null==n?function(){try{var n;return s&&(n=s.randomBytes)?n=n(l):(n=new Uint8Array(l),(f.crypto||f.msCrypto).getRandomValues(n)),S(n)}catch(n){var t=f.navigator,r=t&&t.plugins;return[+new Date,f,r,f.screen,S(a)]}}():n,3),o),u=new m(o);return e.int32=function(){return 0|u.g(4)},e.quick=function(){return u.g(4)/4294967296},e.double=e,j(S(u.S),a),(t.pass||r||function(n,t,r,e){return e&&(e.S&&v(e,u),n.state=function(){return v(u,{})}),r?(c[p]=n,t):n})(e,i,"global"in t?t.global:this==c,t.state)}function m(n){var t,r=n.length,u=this,e=0,o=u.i=u.j=0,i=u.S=[];for(r||(n=[r++]);e<l;)i[e]=e++;for(e=0;e<l;e++)i[e]=i[o=h&o+n[e%r]+(t=i[e])],i[o]=t;(u.g=function(n){for(var t,r=0,e=u.i,o=u.j,i=u.S;n--;)t=i[e=h&e+1],r=r*l+i[h&(i[e]=i[o=h&o+t])+(i[o]=t)];return u.i=e,u.j=o,r})(l)}function v(n,t){return t.i=n.i,t.j=n.j,t.S=n.S.slice(),t}function j(n,t){for(var r,e=n+"",o=0;o<e.length;)t[h&o]=h&(r^=19*t[h&o])+e.charCodeAt(o++);return S(t)}function S(n){return String.fromCharCode.apply(0,n)}if(j(c.random(),a),"object"==typeof module&&module.exports){module.exports=n;try{s=require("crypto")}catch(n){}}else"function"==typeof define&&define.amd?define(function(){return n}):c["seed"+p]=n}("undefined"!=typeof self?self:this,[],Math);
function randIntWithSeed(seed, max=1) {
/* returns a random number between [0,max] including zero and max
seed can be either string or integer */
return Math.round(new Math.seedrandom('seed' + seed)()) * max
}
测试此代码的真正随机性:https ://es6console.com/kkjkgur2/
Math.random
不,但是运行库解决了这个问题。它几乎具有您可以想象的所有分布,并支持种子随机数生成。例子:
ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)
export function createDeterministicRandom(): () => number {
let seed = 0x2F6E2B1;
return function() {
// Robert Jenkins’ 32 bit integer hash function
seed = ((seed + 0x7ED55D16) + (seed << 12)) & 0xFFFFFFFF;
seed = ((seed ^ 0xC761C23C) ^ (seed >>> 19)) & 0xFFFFFFFF;
seed = ((seed + 0x165667B1) + (seed << 5)) & 0xFFFFFFFF;
seed = ((seed + 0xD3A2646C) ^ (seed << 9)) & 0xFFFFFFFF;
seed = ((seed + 0xFD7046C5) + (seed << 3)) & 0xFFFFFFFF;
seed = ((seed ^ 0xB55A4F09) ^ (seed >>> 16)) & 0xFFFFFFFF;
return (seed & 0xFFFFFFF) / 0x10000000;
};
}
你可以像这样使用它:
const deterministicRandom = createDeterministicRandom()
deterministicRandom()
// => 0.9872818551957607
deterministicRandom()
// => 0.34880331158638
我编写了一个返回种子随机数的函数,它使用 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;
}
在 PHP 中,有一个函数srand(seed)
可以为特定的种子生成固定的随机值。但是,在 JS 中,没有这样的内置函数。
但是,我们可以编写简单而简短的函数。
第1步:选择一些种子(固定编号)。
var seed = 100;
数字应为正整数且大于 1,步骤 2中进一步说明。
第2步:在Seed上执行Math.sin()函数,它将给出该数字的sin 值。将此值存储在变量 x中。
var x;
x = Math.sin(seed); // Will Return Fractional Value between -1 & 1 (ex. 0.4059..)
sin()方法返回一个介于 -1 和 1 之间的分数。
我们不需要负值,因此,在第一步中选择大于 1 的数字。
第3步:返回值是介于 -1 和 1 之间的小数值。
因此,将此值乘以 10 使其大于 1。
x = x * 10; // 10 for Single Digit Number
第4步:将值乘以 10 以获得更多位数
x = x * 10; // Will Give value between 10 and 99 OR
x = x * 100; // Will Give value between 100 and 999
根据位数的要求相乘。
结果将是十进制的。
第5步:通过 Math's Round ( Math.round() ) 方法删除小数点后的值。
x = Math.round(x); // This will give Integer Value.
第6步:通过Math.abs方法将负值转换为正值(如果有)
x = Math.abs(x); // Convert Negative Values into Positive(if any)
解释结束。
最终代码
var seed = 111; // Any Number greater than 1
var digit = 10 // 1 => single digit, 10 => 2 Digits, 100 => 3 Digits and so. (Multiple of 10)
var x; // Initialize the Value to store the result
x = Math.sin(seed); // Perform Mathematical Sin Method on Seed.
x = x * 10; // Convert that number into integer
x = x * digit; // Number of Digits to be included
x = Math.round(x); // Remove Decimals
x = Math.abs(x); // Convert Negative Number into Positive
干净和优化的功能代码
function random_seed(seed, digit = 1) {
var x = Math.abs(Math.round(Math.sin(seed++) * 10 * digit));
return x;
}
random_seed(any_number, number_of_digits)
然后使用any_number调用此函数是必须的并且应该大于 1。
number_of_digits是可选参数,如果没有传递,则返回 1 Digit。
random_seed(555); // 1 Digit
random_seed(234, 1); // 1 Digit
random_seed(7895656, 1000); // 4 Digit
对于 0 到 100 之间的数字。
Number.parseInt(Math.floor(Math.random() * 100))