【问题标题】:Better random function in JavaScriptJavaScript 中更好的随机函数
【发布时间】:2013-05-28 21:20:26
【问题描述】:

我目前正在用 JavaScript 再现康威的生命游戏,我注意到函数 Math.random() 总是返回某种模式。以下是 100x100 网格中的随机结果示例:

有人知道如何获得更好的随机数吗?

ApplyRandom: function() {

    var $this = Evolution;

    var total = $this.Settings.grid_x * $this.Settings.grid_y;
    var range = parseInt(total * ($this.Settings.randomPercentage / 100));

    for(var i = 0; i < total; i++) {
      $this.Infos.grid[i] = false;
    }

    for(var i = 0; i < range; i++) {
      var random = Math.floor((Math.random() * total) + 1);
      $this.Infos.grid[random] = true;
    }

    $this.PrintGrid();
  },

[更新]

我在这里创建了一个 jsFiddle:http://jsfiddle.net/5Xrs7/1/

[更新]

看来 Math.random() 毕竟没问题(感谢raina77ow)。对不起各位! :(。如果你对结果感兴趣,这里是游戏的更新版本:http://jsfiddle.net/sAKFQ/

(但我认为还有一些错误......)

【问题讨论】:

  • 你能给我们看一些代码@Danny吗?
  • 赞成@Dogbert,看起来确实像一个XY问题。 Math.random() 本身非常随机。它的结果可能是可以预测的(如论文here 所示),但它绝对不遵循此处所示的如此清晰的模式。还有其他问题。
  • 其实我冒昧地创建了这样一个fiddle。正如预期的那样,Math.random() 给出了相当混乱的模式。我什至没有修复原始代码的一个错误,按原样使用它。
  • 我知道这个问题现在已经解决了,毕竟Math.Random() 不是问题,但为了其他遇到类似问题的人的利益,值得重复一句格言:任何时候你认为你在你正在使用的库中发现了一个错误,它实际上是你自己的代码中的一个错误的可能性非常高,而不是在核心库中。杰夫阿特伍德说得比我说的好得多,所以我将在这里链接到他的博客:The 1st Rule Of Programming: It's Always Your Fault

标签: javascript math random


【解决方案1】:

你可以试试

有关Math.random() 强度的讨论,请参阅this question

【讨论】:

    【解决方案2】:

    Math.random 是一种伪随机方法,这就是您得到这些结果的原因。我经常使用的绕过方法是捕捉鼠标光标位置,以便为 Math.random 结果添加一些盐:

    Math.random=(function(rand) {
      var salt=0;
      document.addEventListener('mousemove',function(event) {
        salt=event.pageX*event.pageY;
        });
    return function() { return (rand()+(1/(1+salt)))%1; };
    })(Math.random);
    

    这不是完全随机的,而是更多;)

    【讨论】:

      【解决方案3】:

      更好的解决方案可能不是随机选择点并将它们涂成黑色,而是遍历每个点,确定应该填充的几率,然后进行相应的填充。 (也就是说,如果你希望它平均有 20% 的机会被填充,请生成你的随机数 r 并在 r &lt; 0.2 时填充..IIRC。

      编辑:这是考虑替代绘画方法的另一个原因。虽然随机选择像素最终可能会减少工作量并减少对随机数生成器的调用,这可能是一件好事,这取决于你想要什么。事实上,您似乎选择了一种方式,至多将填充一定百分比的像素。如果您一直跟踪正在填充的像素,并且如果已经填充了另一个像素,则选择填充另一个像素,那么基本上您所做的就是在您的白色像素中打乱精确百分比的黑色像素。按照我的方式去做,所选像素的百分比将遵循二项分布。有时填充的百分比会多一点,有时会少一点。所有洗牌的集合是产生这种选择的可能性的严格子集(严格来说,它也包含绘制棋盘的所有可能性,只是获得其中大部分的可能性极低) .简而言之,为每个像素随机选择将允许更多的变化。

      然后,我可以修改 shuffle 算法以根据二项式概率分布函数生成的数字选择像素百分比,该函数具有定义的预期/平均值而不是预期/平均值本身,老实说,我不知道这与使用预期/平均值本身对每个像素运行赔率有什么不同——至少在理论上是这样。有很多事情可以做。

      【讨论】:

      • 是的,这是更好的解决方案。看看原始代码有什么问题仍然会很有趣。 )
      【解决方案4】:

      Math.random 的实现可能基于linear congruential generator,其中一个弱点是随机数取决于较早的值,从而产生这样的可预测模式,具体取决于算法中常数的选择。在RANDU 中可以看到一个著名的常量选择不当影响的例子。

      Mersenne Twister 随机数生成器没有这个弱点。例如,您可以在 JavaScript 中找到 MT 的实现:https://gist.github.com/banksean/300494


      更新:看到您的代码,您在呈现网格的代码中存在问题。这一行:

      var position = (y * 10) + x;
      

      应该是:

      var position = (y * grid_x) + x;
      

      通过此修复,没有明显的模式。

      【讨论】:

        【解决方案5】:

        代码中的这一行...

        var position = (y * 10) + x;
        

        ... 是造成这种“非随机性”的原因。真的应该是……

        var position = (y * $this.Settings.grid_x) + x;
        

        我想10 是这个网格的原始大小,这就是它在这里的原因。但这显然是错误的:您应该根据网格的当前大小来选择您的位置。


        作为旁注,没有冒犯,但我仍然认为@JayC 答案中给出的算法优于你的算法。而且很容易实现,只需将ApplyRandom函数中的两个循环改为一个即可:

        var bias = $this.Settings.randomPercentage / 100;
        for (var i = 0; i < total; i++) {
          $this.Infos.grid[i] = Math.random() < bias;
        }
        

        通过此更改,您将不再受到在 var random = Math.floor((Math.random() * total) + 1); 行中重复使用相同数字的副作用,这会降低原始代码中的实际单元格填充率。

        【讨论】:

        • 作为旁注,我不敢相信这里有多少人似乎认为问题中显示的模式确实是Math.random() 非随机性的结果。
        • 不,这与你无关。 ) 有时当我使用扩展代码库并且出现问题时,我也开始怀疑基本原理,所以我完全理解你的感受。 )
        • y 的乘数应该是grid_x,即网格线的宽度。目前它们是相等的,所以没有区别,但如果尺寸是非方形的,那么就会失败。
        • 另外,您在ApplyRandom 的末尾调用PrintGrid来自Init 中的代码。当PrintGrid 修改网格时,您作为初始步骤显示的实际上是第一次迭代后的状态。
        • @DannyCoulombe 为了获得更好的程序结构,您应该移动代码以将游戏从PrintGrid 移至新函数。
        【解决方案6】:

        您可以使用时间戳中的部分 sha256 哈希,包括纳秒:

        console.log(window.performance.now()); //return nanoseconds inside
        

        这可以编码为字符串, 然后你可以得到哈希,使用这个:http://geraintluff.github.io/sha256/

        salt = parseInt(sha256(previous_salt_string).substring(0, 12), 16);
        //48 bits number < 2^53-1
        

        然后,使用来自@nfroidure 的函数, 之前写过 gen_salt 函数,在那里使用 sha256 哈希, 并将 gen_salt 调用写入 eventListener。 您可以使用 sha256(previous_salt) + 鼠标坐标作为字符串来获取随机哈希。

        【讨论】:

          【解决方案7】:

          console.log(window.crypto.getRandomValues(new Uint8Array(32))); //return 32 random bytes

          这会返回一个具有加密强度的随机字节:https://developer.mozilla.org/en/docs/Web/API/Crypto/getRandomValues

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-03-26
            • 2021-06-28
            • 1970-01-01
            • 2016-11-03
            • 1970-01-01
            • 2010-12-27
            • 2013-03-26
            • 1970-01-01
            相关资源
            最近更新 更多