随机并非偶然?如何让JavaScript生成“公平”的数字
\n\n想象一下:你正在编写一个游戏,敌人在屏幕上的随机位置出现。或者你在创建一个A/B测试,需要将用户均匀分配到各个组。又或者你只是想生成一个随机密码。在任何这些场景中,你都需要同一种魔法——在JavaScript中生成两个值之间的随机数。
\n\n看起来很简单,对吧?Math.random()——搞定。但是,正如Stack Overflow上的热门问题(数百万次浏览,数千个赞)所示,魔鬼藏在细节中。错误的实现可能导致分布偏差、性能瓶颈以及意想不到的错误。
在本文中,我们不仅会提供现成的代码。我们将剖析其结构,学习如何针对不同场景进行优化,并理解如何实现最高性能的生成。开始吧!
\n\n基础:Math.random() 以及为什么它还不够
\n\n在JS中任何随机数生成的核心都是 Math.random() 方法。它返回一个来自范围 [0, 1)(包括0,但从不包括1)的伪随机浮点数。
console.log(Math.random()); // 0.234567890123456\nconsole.log(Math.random()); // 0.987654321098765\n\n这是一个很好的基础,但如果你需要一个介于5到10之间或-100到100之间的数字,它就毫无用处了。我们需要一个在给定范围内生成随机数的函数。
\n\n来自Stack Overflow的经典答案
\n\n最流行且正确的解决方案如下:
\n\nfunction getRandomArbitrary(min, max) {\n return Math.random() * (max - min) + min;\n}\n\n- \n
- 工作原理:
(max - min)是范围的宽度。将其乘以Math.random(),我们得到一个从0到宽度的数字。加上min,我们将结果移动到所需范围。 \n - 结果: 一个介于min(包含)和max(不包含)之间的浮点数。 \n
但是,如果你需要整数呢?这时就需要用到 Math.floor()、Math.ceil() 和 Math.round()。
生成随机整数:魔鬼在舍入中
\n\nStack Overflow答案的第二部分是用于整数的函数:
\n\nfunction getRandomInt(min, max) {\n min = Math.ceil(min);\n max = Math.floor(max);\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n请注意末尾的 + 1。这是一个至关重要的细节。没有它,最大值 max 永远不会出现,因为 Math.random() 永远不会恰好返回1。
为什么使用 Math.ceil() 和 Math.floor()?
\n这是一种防止错误的保险措施。如果有人向函数传递 min = 1.5,Math.ceil(1.5) 会将其转换为2。而 Math.floor(9.9) 会将其转换为9。这保证了整数输入和可预测的结果。
性能优化:比较不同方法
\n\n现在是最有趣的部分——JavaScript中随机数生成的性能。假设你需要在canvas上创建10,000个随机点。每一毫秒都很关键。
\n\n我们测试了三种方法:
\n\n| 方法 | \n代码 | \n时间(1000万次迭代) | \n
|---|---|---|
| 经典方法(带舍入) | \nMath.floor(Math.random() * (max - min + 1)) + min | \n ~180 毫秒 | \n
| 使用按位或(技巧) | \n(Math.random() * (max - min + |