为什么不建议使用 Math.random 生成随机数

2,836 阅读2分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

前言

日常开发中大家经常使用 Math.random 来实现随机的效果,但是你知道 Math.random 实际上是“伪随机”吗?除了 Math.random 外,有什么方式可以实现真随机的?大家可以带着这些疑问继续阅读。

Math.random

介绍

Math.random()  函数返回一个浮点数,伪随机数在范围从0 到小于1,也就是说,从 0(包括 0)往上,但是不包括 1(排除 1),然后您可以缩放到所需的范围。

语法

Math.random()

返回值

返回一个浮点型伪随机数字,在0(包括 0)和1(不包括)之间。

Mat为.random 真的随机吗

我们可以简单测试一下 Math.random 生成数字的分布概率。这里我们可以使用一个数组来进行统计:

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const locationMap = new Array(10).fill(0);

const shuffle = (nums) => {
    const lens = nums.length;
    nums.sort(() => Math.random() - 0.5);
    locationMap[nums[lens - 1] - 1]++;
}

for (let i = 0; i < 100000; ++i) {
    shuffle(arr.slice());
}

console.log(locationMap);

log:
[
  10199, 2629,  8036,
   7046, 9260, 12724,
  16780, 9567, 10967,
  12792
]

我们可以看出元素在各个位置出现的概率差距有可能是巨大的。因此通过 Math.random 打乱的数组并不是真正的随机。

我们还可以从这引申出另一个问题:很多面试题合集里会有打乱数组这么一道题,大部分题解用的都是 nums.sort(() => Math.random() - 0.5) 这个手段去实现“随机”打乱的,如果面试官好说话倒还好,如果面试官有意刁难,小伙伴们有办法写出真正随机的打乱数组方法吗?

为了做到真随机打乱数组,我们首先要做到元素在各个位置的出现的概率是一样或者近似的。

洗牌算法

洗牌算法的步骤就是将数组分为 未排序区域 和 已排序区域,不断的从未排序区域中 随机 出一个下标来,和未排序区域的最后一个元素 交换 ,进行 n 轮交换后就实现了等概率的洗牌算法了。

const shuffle = (arr) => {
    const lens = arr.length;
    for (let i = 0; i < lens; ++i) {
        const idx = Math.floor(Math.random() * (lens - i));
        [arr[idx], arr[lens - i - 1]] = [arr[lens - i - 1], arr[idx]];
    }
    return arr;
}

const arr = [6, 10, 4, 2, 7];
console.log(shuffle(arr));

由于洗牌算法不是本文的重点,这里就简单介绍一下。

crypto.getRandomValues

介绍

crypto.getRandomValues 方法让你可以获取 符合密码学要求的安全的随机值 。传入参数的数组被随机值填充。

语法

cryptoObj.getRandomValues(typedArray);

示例

/* 假设 window.crypto.getRandomValues 可用 */

var array = new Uint32Array(1);
window.crypto.getRandomValues(array);

我们看看输出:

微信截图_20221109201006.png

封装函数

接下来我们封装一个获取随机数的方法:

const myRandom = function (){
  const array = new Uint32Array(1);
  const randomNum = crypto.getRandomValues(array)[0] / 0xffffffff;
  return `${randomNum}`[2];
}

结束语

如果小伙伴们有别的想法,欢迎留言,让我们共同学习进步💪💪。

如果文中有不对的地方,或是大家有不同的见解,欢迎指出🙏🙏。

如果大家觉得所有收获,欢迎一键三连💕💕。