JavaScript中的Math.random()如何工作?

32

我最近发现可以通过谷歌获取随机数,这使我想到了 Math.random() 如何工作。所以我在这里,除非它们使用类似时间的东西,否则我无法理解 Math.random() 是如何实现的,是否有人知道 JavaScript 的 Math.random() 如何工作或等价的方法?


3
它的工作方式并不受规范的控制。在大多数运行时中,它可能是某种线性同余生成器。 - Pointy
1
请参见https://dev59.com/gGPVa4cB1Zd3GeqP3jrY。 - GluePear
1
注意:ECMA 描述指出,数字可以随机或伪随机生成,并且在不同平台上可能会有所不同。 - rolfv1
可能是理解“随机性”的重复问题。 - Cole Tobin
你是通过谷歌获取随机数的吗?那么你应该在那里发布一个答案... - Bergi
显示剩余2条评论
5个回答

33

Math.random()返回一个数值,其为大于或等于0但小于1的正数,使用基于实现的算法或策略以近似均匀分布地选择随机或伪随机值。

以下是V8的实现方式:

uint32_t V8::Random() {

    // Random number generator using George Marsaglia's MWC algorithm.
    static uint32_t hi = 0;
    static uint32_t lo = 0;

    // Initialize seed using the system random(). If one of the seeds
    // should ever become zero again, or if random() returns zero, we
    // avoid getting stuck with zero bits in hi or lo by reinitializing
    // them on demand.
    if (hi == 0) hi = random();
    if (lo == 0) lo = random();

    // Mix the bits.
    hi = 36969 * (hi & 0xFFFF) + (hi >> 16);
    lo = 18273 * (lo & 0xFFFF) + (lo >> 16);
    return (hi << 16) + (lo & 0xFFFF);
}

来源: http://dl.packetstormsecurity.net/papers/general/Google_Chrome_3.0_Beta_Math.random_vulnerability.pdf

以下是StackOverflow上的相关主题:


3
这是旧版实现,请参阅 https://dev59.com/SWIj5IYBdhLWcg3wk182#52191522 获取最新版本。 - John Leidegren

11

参见:There's Math.random(), and then there's Math.random()

直到最近(版本4.9.40之前),V8选择了MWC1616(乘法带进位,组合两个16位部分)作为其伪随机数生成器。它使用64位内部状态,并且大致如下:

uint32_t state0 = 1;
uint32_t state1 = 2;
uint32_t mwc1616() {
  state0 = 18030 * (state0 & 0xffff) + (state0 >> 16);
  state1 = 30903 * (state1 & 0xffff) + (state1 >> 16);
  return state0 << 16 + (state1 & 0xffff);

然后将32位值转换为浮点数,其范围在0和1之间并符合规定。

MWC1616使用的内存较少且计算速度相当快,但不幸的是提供的质量低于标准:

  • 它可以生成的随机值数量仅限于232,而双精度浮点数可表示0到1之间的252个数字。
  • 结果的更重要的上半部分几乎完全取决于state0的值。周期长度最多为232,但除了少数大型置换周期外,还有许多短周期。如果初始状态选择不当,则循环长度可能小于4000万。
  • 它未通过TestU01套件中的许多统计测试。

我们已经注意到了这一点,并在了解问题并进行一些研究后,决定基于名为xorshift128+的算法重新实现Math.random。它使用128位内部状态,具有2 ^ 128-1的周期长度,并通过来自TestU01套件的所有测试。

uint64_t state0 = 1;
uint64_t state1 = 2;
uint64_t xorshift128plus() {
  uint64_t s1 = state0;
  uint64_t s0 = state1;
  state0 = s0;
  s1 ^= s1 << 23;
  s1 ^= s1 >> 17;
  s1 ^= s0;
  s1 ^= s0 >> 26;
  state1 = s1;
  return state0 + state1;
}

我们意识到这个问题后的几天内,新的实现已经在V8 4.9.41.0中发布。它将与Chrome 49一起提供。Firefox和Safari也同时采用了xorshift128+。


6

他们使用"像时间一样的东西"是正确的。伪随机生成器通常使用系统时钟作为种子,因为这是一个好的数字来源,不会始终相同。

一旦用数字播种了随机生成器,它就会生成一系列数字,所有数字都依赖于初始值,但以看起来随机的方式生成。

一个简单的随机生成器(实际上曾经在编程语言中使用)就是使用素数在类似以下算法:

rnd = (rnd * 7919 + 1) & 0xffff;

这将产生一系列似乎随机反复跳动的数字。例如:
seed = 1337
36408
22089
7208
63833
14360
11881
41480
13689
6648

Javascript中的随机生成器要稍微复杂一些(为了获得更好的分布),使用了更大的数字(因为它必须生成一个约60位而不是16位的数字),但它遵循相同的基本原理。

你能提供一个来源吗? - Choylton B. Higginbottom
我不知道种子的目的是什么,这难道不会使得后续值依赖于初始值/先前值吗? - Alexander Mills

1

您可能需要参考这篇文章:https://hackernoon.com/how-does-javascripts-math-random-generate-random-numbers-ef0de6a20131

顺便提一下,最近我也对这个问题很好奇,然后阅读了NodeJS的源代码。我们可以从Google V8中知道一种可能的实现方式:

随机数的主要入口(MathRandom::RefillCache函数)如下: https://github.com/v8/v8/blob/master/src/math-random.cc

如何初始化种子?请参见此处:https://github.com/v8/v8/blob/master/src/base/utils/random-number-generator.cc#L31

关键函数是 (XorShift128 函数): https://github.com/v8/v8/blob/master/src/base/utils/random-number-generator.h#L119

在这个头文件中,引用了一些论文:

// See Marsaglia: http://www.jstatsoft.org/v08/i14/paper
// And Vigna: http://vigna.di.unimi.it/ftp/papers/xorshiftplus.pdf

-4
<script>

function generateRandom(){  //  Generate and return a random number
    var num = Math.random();
    num = (Math.round((num*10)))%10;
    return num;
}

function generateSum(){ //  Generate a problem
    document.getElementById("ans").focus();
    var num1 = generateRandom();
    var num2 = generateRandom();
    document.getElementById("num1").innerHTML = num1;
    document.getElementById("num2").innerHTML = num2;
    document.getElementById("pattern1").innerHTML = printPattern(num1);
    document.getElementById("pattern2").innerHTML = printPattern(num2);

}

function printPattern(num){ //  Generate the star pattern with 'num' number of stars
    var pattern = "";
    for(i=0; i<num; i++){
        if((i+1)%4 == 0){
            pattern = pattern+"*<br>";
        }
        else{
            pattern = pattern+"*";
        }
    }
    return pattern;
}

function checkAns(){    //  Check the answer and give the response
    var num1 = parseInt(document.getElementById("num1").innerHTML);
    var num2 = parseInt(document.getElementById("num2").innerHTML);
    var enteredAns = parseInt(document.getElementById("ans").value);
    if ((num1+num2) == enteredAns){
        document.getElementById("patternans").innerHTML = printPattern(enteredAns);
        document.getElementById("patternans").innerHTML += "<br>Correct";
    }
    else{
        document.getElementById("patternans").innerHTML += "Wrong";
        //remove + mark to remove the error

    }
}

function newSum(){
    generateSum();
    document.getElementById("patternans").innerHTML = "";
    document.getElementById("ans").value = "";
}

</script>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接