是否可以在 JavaScript 中播种随机数生成器 (Math.random
)?
不,不可能播种 Math.random()
。 ECMAScript specification 在主题上故意含糊其辞,既不提供播种方式,也不要求浏览器甚至使用相同的算法。所以这样的功能必须由外部提供,幸好这并不难。
我已经用纯 JavaScript 实现了许多好的、短的和快速的 伪随机数生成器 (PRNG) 函数。所有这些都可以播种并提供高质量的数字。这些并非出于安全目的 - 如果您需要可播种的 CSPRNG,请查看 ISAAC。
首先,注意正确初始化您的 PRNG。为简单起见,下面的生成器没有内置种子生成过程,但接受一个或多个 32 位数字作为 PRNG 的初始种子状态。相似或稀疏的种子(例如 1 和 2 的简单种子)具有低熵,并且可能导致相关性或其他随机性质量问题,有时会导致输出具有相似的属性(例如随机生成的级别相似)。为避免这种情况,最佳实践是使用分布良好的高熵种子和/或超过前 15 个左右的数字来初始化 PRNG。
有很多方法可以做到这一点,但这里有两种方法。首先,hash functions 非常擅长从短字符串生成种子。即使两个字符串相似,一个好的哈希函数也会产生非常不同的结果,因此您不必对字符串花太多心思。这是一个示例哈希函数:
function cyrb128(str) {
let h1 = 1779033703, h2 = 3144134277,
h3 = 1013904242, h4 = 2773480762;
for (let i = 0, k; i < str.length; i++) {
k = str.charCodeAt(i);
h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
}
h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
return [(h1^h2^h3^h4)>>>0, (h2^h1)>>>0, (h3^h1)>>>0, (h4^h1)>>>0];
}
调用 cyrb128
将从可用于播种 PRNG 的字符串中生成 128 位哈希值。以下是您可以如何使用它:
// Create cyrb128 state:
var seed = cyrb128("apples");
// Four 32-bit component hashes provide the seed for sfc32.
var rand = sfc32(seed[0], seed[1], seed[2], seed[3]);
// Only one 32-bit component hash is needed for mulberry32.
var rand = mulberry32(seed[0]);
// Obtain sequential random numbers like so:
rand();
rand();
注意:如果您想要更强大的 128 位哈希,请考虑 MurmurHash3_x86_128,它更彻底,但适用于大型数组。
或者,只需选择一些虚拟数据来填充种子,并预先将生成器推进几次(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 到 232-1),然后将其转换为 0-1 之间的浮点数(包括 0,不包括 1 ) 等价于 Math.random()
,如果您想要特定范围的随机数,请阅读 this article on 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
和 >>>= 0
是干什么用的。这些本质上是 32 位整数转换,用于性能优化。 JS 中的 Number
基本上是浮点数,但在按位运算时,它们会切换到 32 位整数模式。这种模式被 JS 解释器处理得更快,但是任何乘法或加法都会导致它切换回浮点数,从而导致性能下降。
桑树32
Mulberry32 是一个具有 32 位状态的简单生成器,但速度极快且具有良好的随机性(作者声称它通过了 gjrand 测试套件的所有测试,并且具有完整的 232 周期,但是我没有验证)。
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 并且不需要数十亿个随机数(请参阅 Birthday problem),我会推荐这个。
xoshiro128**
自 2018 年 5 月起,xoshiro128** 成为 Xorshift family 的新成员,由 Vigna & Blackman(Vigna 教授还负责为大多数 Math.random
实现提供支持的 Xorshift128+ 算法)。它是提供 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;
}
}
作者声称它很好地通过了随机性测试 (albeit with caveats)。其他研究人员指出,它未能通过 TestU01 中的一些测试(尤其是 LinearComp 和 BinaryRank)。在实践中,使用浮点数时(例如在这些实现中)不应该导致问题,但如果依赖原始最低位可能会导致问题。
JSF(詹金斯的小快)
这是 Bob Jenkins (2007) 的 JSF 或“smallprng”,他还制作了 ISAAC 和 SpookyHash。它 passes 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;
}
}
不,不可能播种 Math.random()
,但编写自己的生成器相当容易,或者更好的是,使用现有的生成器。
此外,请参阅 David Bau 的博客以了解 more information on seeding。
注意:尽管(或者更确切地说,因为)简洁和明显的优雅,这个算法在随机性方面绝不是一个高质量的算法。寻找例如this answer中列出的那些以获得更好的结果。
(最初改编自对另一个答案的评论中提出的一个聪明的想法。)
var seed = 1;
function random() {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
您可以将 seed
设置为任意数字,但要避免零(或 Math.PI 的任意倍数)。
在我看来,这个解决方案的优雅之处在于缺少任何“神奇”数字(除了 10000,它表示为避免出现奇怪的模式而必须丢弃的最少数字数量 - 查看值为 10、{ 2},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;
}
m_w
,而不是 m_z
。 2) m_w
和 m_z
都根据它们以前的值进行更改,因此它确实修改了结果。
Antti Sykä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 没有的另一个功能:多个独立的随机生成器。如果您想同时运行多个可重复的模拟,这一点尤其重要。
Math.random
,这将允许您拥有多个独立的生成器,对吗?
Math.seed(42);
时都会重置函数,因此如果执行 var random = Math.seed(42); random(); random();
,则会得到 0.70...
,然后是 0.38...
。如果您通过再次调用 var random = Math.seed(42);
来重置它,那么下次您调用 random()
时,您将再次获得 0.70...
,下一次您将再次获得 0.38...
。
random
的局部变量,而不是覆盖原生 javascript 函数。覆盖 Math.random
可能会导致 JIST 编译器未优化您的所有代码。
请参阅 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();
Math.seed(0)()
返回 0.2322845458984375
,Math.seed(1)()
返回 0.23228873685002327
。根据种子同时更改 m_w
和 m_z
似乎有帮助。 var m_w = 987654321 + s; var m_z = 123456789 - s;
产生具有不同种子的第一个值的良好分布。
编写自己的伪随机生成器非常简单。
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))
另请注意,当需要真实熵时,您不能使用伪随机序列。
Math.random
没有,但 ran library 解决了这个问题。它几乎具有您可以想象的所有分布,并支持种子随机数生成。例子:
ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)
不可能播种内置的 Math.random 函数,但可以用很少的代码在 Javascript 中实现高质量的 RNG。
Javascript 数字是 64 位浮点精度,可以表示小于 2^53 的所有正整数。这对我们的算术设置了一个硬性限制,但在这些限制内,您仍然可以为高质量的 Lehmer / LCG 随机数生成器选择参数。
function RNG(seed) {
var m = 2**35 - 31
var a = 185852
var s = seed % m
return function () {
return (s = s * a % m) / m
}
}
Math.random = RNG(Date.now())
如果您想要更高质量的随机数,但要以慢约 10 倍为代价,您可以使用 BigInt 进行算术并选择 m 刚好适合双精度数的参数。
function RNG(seed) {
var m_as_number = 2**53 - 111
var m = 2n**53n - 111n
var a = 5667072534355537n
var s = BigInt(seed) % m
return function () {
return Number(s = s * a % m) / m_as_number
}
}
有关上述实现中使用的参数,请参见 Pierre l'Ecuyer 的这篇论文:https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf
无论您做什么,都要避免使用 Math.sin 的所有其他答案!
这是采用的 Jenkins 哈希版本,借用自 here
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
这里的大多数答案都会产生有偏见的结果。所以这是一个基于 seedrandom library from github 的测试函数:
!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.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 的 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 位。
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))
Math.random
,这样每当 Math.random
播种相同的种子时,它将产生相同的连续随机数系列。据说,这个问题与 Math.random
的实际使用/演示无关。
不定期副业成功案例分享
seed = (seed * 185852 + 1) % 34359738337
。Math.imul
允许它溢出,就像在 C 中对 32 位整数使用乘法一样。您所建议的是使用 JS 整数空间的全部范围的 LCG,这绝对是一个值得探索的有趣领域。 :)