本帖最后由 Yiero 于 2023-11-9 14:28 编辑
随机整数的生成
Math.random()
函数会随机生成 [0, 1) 中间的任意一个小数(包括0不包括1) , 通过这个性质我们可以做一个类似于百分比的计算:
百分比计算
目标: 生成 [0, 40) 的随机整数
现在随便调用一次 Math.random() // -> 0.6443368920606216
, 我们暂且将其视为 64%
.
因为我们要生成 [0, 40) 的随机整数, 那么我们可以计算: 40 * 64% = 25.6 ≈ 25
.
于是这一次随机中, 我们随机生成的数字就是 25.
基于这个逻辑, 我们可以写出以下代码:
const randomNumber = Math.random(); // -> 0.6443368920606216
const randomFloat = 40 * randomNumber; // -> 25.773475682424866
const randomInt = Math.floor(randomFloat); // -> 25
关于这里的为什么使用的是向下取整 Math.floor()
而不是四舍五入 Math.round()
, 后文会再进行解释.
更加通用的百分比计算
由于上文中生成的数字比较的简单并且是整数, 所以计算逻辑会比较的简单, 为了更加的通用, 我们换一个场景:
目标: 生成 [10, 45) 的随机整数
虽然换了一个场景, 但是逻辑是一样, 还是一样的百分比问题.
我们可以将最小数 10 提取出来, 然后将这个问题看成是一个生成 [0, 35) 的随机整数的问题, 然后我们就可以使用上文的逻辑去解决问题了:
const randomNumber = Math.random(); // -> 0.9273800879651488
const randomFloat = (45 - 10) * randomNumber; // -> 32.45830307878021
const randomInt = Math.floor(randomFloat); // -> 32
// 最终生成的随机整数是 42
const resultInt = 10 + randomInt; // 42
包含最大值的随机数生成
在前文中我们生成的都是不包括最大数的随机整数, 那么我们该如何生成包括最大数的随机整数呢?
首先我们要讲到为什么之前的算法逻辑不能生成包括最大数的随机整数, 这跟上文我们提到的不使用四舍五入有关.
JavaScript中的取整算法
还是上文的百分比计算法, Math.random()
可以输出 0% ~ 99.999%
的值, 虽然不能直接输出 1 但是无伤大雅因为它无限接近 1 .
这主要是三个取整方法的区别: 向下取整 Math.floor()
, 四舍五入 Math.round()
和 向上取整 Math.ceil()
.
这里我们直接把百分比拿来举例: 总共有 0%
, 10%
, 20%
, ... , 80%
, 90%
, 100%
这11个整值百分比是我们需要的值, 然后 Math.random()
能够输出的是 0 ~ 99.999%
.
假设取整函数是十位数取整, (四舍五入情况下)也就是 0% ~ 4%
取整到 0%
; 5% ~ 14%
取整到 10%
, 15% ~ 24%
取整到 20%
, ... , 95% ~ 100%
取整到 100%
.
- 使用向下取整, 能得到都是 10% 概率拿到的
0%
, 10%
, ... ,90%
的十个整值百分比 (除了 100%
都能拿到, 也就是没有最大值);
- 使用向上取整, 可以拿到都是 10% 概率拿到的
10%
, ... ,90%
, 100%
的十个整值百分比 (除了 0%
都能拿到, 也就是没有最小值);
- 使用四舍五入, 可以有 5% 概率拿到
0%
, 有 5% 概率获取到 100%
, 剩下的9个值都是 10% 的概率获取到 (11个整值百分比都能获取到, 但是概率不能均分).
通过下面这个函数, 我们可以验证具体情况是否是上文所述:
/**
* @param {number[]} numberList 需要统计数字数组
* @Return {{[num: string]: string}} 返回数字数组中每个数字出现的占比(百分比)
*/
const calc = (numberList) => {
const result = {}
numberList.forEach((item) => {
result[item] = result[item] ? result[item] + 1 : 1;
});
const values = Object.values(result);
const sum = values.reduce((total, current) => total += current, 0);
for (let resultKey in result) {
result[resultKey] = Math.round(result[resultKey] / sum * 100) + '%'
}
return result;
}
function generate(min, max, getIntMethod) {
let getInt = Math.floor;
switch (getIntMethod) {
case 'floor':
getInt = Math.floor;
break;
case 'ceil':
getInt = Math.ceil;
break;
case 'round':
getInt = Math.round;
break;
}
const floorList = [];
for (let i = 0; i < 100000; i++) {
floorList.push(getInt((max - min) * Math.random()) + min)
}
return floorList;
}
console.log('floor: ',calc(generate(1, 5, 'floor')));
// -> floor: { '1': '25%', '2': '25%', '3': '24%', '4': '25%' }
console.log('round: ',calc(generate(1, 5, 'round')));
// -> round: { '1': '12%', '2': '25%', '3': '25%', '4': '25%', '5': '12%' }
console.log('ceil: ',calc(generate(1, 5, 'ceil')));
// -> ceil: { '2': '25%', '3': '25%', '4': '25%', '5': '25%' }
提升区间
上文提到, 只有 Math.floor()
和 Math.ceil()
才能让生成的随机数概率均分, 但是使用这两个函数又无法取到某一个最值, 那么该如何解决呢?
答案是提升我们取值的区间: 比如当我们想要获取到 [0, 35] 的所有值时, 使用 Math.floor(35 * Math.random())
只能获取到 [0, 34]
的值, 那么我们将最大值加一位, 也就是使用 Math.floor((35 + 1) * Math.random())
, 就能够获取 [0, 35]
的值了.
随机生成整数 randomInt
函数
将上文提到的具体数字抽象成两个变量, min
表示最小值, max
表示最大值即可生成 [min, max]
的值.
const randomInt = (min, max) => {
return Math.floor((max - min + 1) * Math.random()) + min;
}
测试
const floorList = [];
for (let i = 0; i < 100000; i++) {
floorList.push(randomInt(1, 9))
}
// 使用上文提到的 clac 函数
console.log(calc(floorList));
/* ->
{
'1': '11%',
'2': '11%',
'3': '11%',
'4': '11%',
'5': '11%',
'6': '11%',
'7': '11%',
'8': '11%',
'9': '11%'
}
*/