尝试使用 MAX_SAFE_INTEGER 生成数字时,我注意到一些奇怪的事情,我确信这与数字在 JavaScript 中存储的方式有关,但我不明白它到底是什么。
// Always returns an odd number
Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
// Returns an odd number 75% of the time
Math.floor(Math.random() * (Number.MAX_SAFE_INTEGER - 1))
// Has a 50/50 chance to return odd or even
Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)
如何解释这种行为以及在
Math.floor
中可以使用的最大整数是多少才能获得 50/50 比率?
let evenCount = 0, oddCount = 0;
for (let i = 0; i < 10000; i++) {
const randomNumber = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
if (randomNumber % 2 === 0) {
evenCount++;
} else {
oddCount++;
}
}
console.log("Number of even numbers:", evenCount);
console.log("Number of odd numbers:", oddCount);
首先,您应该乘以 253 (
Number.MAX_SAFE_INTEGER + 1
),以从使用全双精度的 Math.random
实现中获取所有 53 位。 253−1 如果它产生任何不同的结果,可能不会造成太大伤害,但我不想进行必要的浮点分析 - 选择明显正确的解决方案要容易得多。
那么问题出在哪里呢?好吧,您的原始代码在 Firefox 和 Safari 上运行良好!只是V8(即Chrome及其衍生品)使用52位而不是53位。
let mostBits = 0;
for (let i = 0; i < 10000; i++) {
const bits = Math.random().toString(2).slice(2).length;
if (bits > mostBits) {
mostBits = bits;
}
}
console.log("Most bits:", mostBits);
(火狐、Safari)
最多位:53
(镀铬)
最多位:52
(可以用 52 位存储的有效数精确存储 53 位的原因是整数部分隐式是 1,可以通过指数缩放到正确的位置,这与为什么
Number.MAX_SAFE_INTEGER
是这样的原因相同.)
看看 V8 实现的相关部分,我认为它这样做的唯一原因是为了性能 - 通过固定指数以使范围 [1, 2),它可以将随机位直接插入到 double 中,而不是必须执行乘法。
static inline double ToDouble(uint64_t state0) { // Exponent for double values for [1.0 .. 2.0) static const uint64_t kExponentBits = uint64_t{0x3FF0000000000000}; uint64_t random = (state0 >> 12) | kExponentBits; return base::bit_cast<double>(random) - 1; }
所以回答你的问题,
为了获得 50/50 的比率,您可以在
中使用的最大整数是多少?Math.floor
最多 252,但我不会数
Math.random
具有超过 32 位的随机性,除非您只针对一个引擎(例如,V8 在 2015 年更改为 52),或者即使对于特定目的来说它具有足够好的随机性——这些东西都不在规范中。
此函数返回一个带正号的数字值,大于或等于 +0 但严格小于 1,随机或伪随机选择,在该范围内近似均匀分布,使用实现定义的算法或策略。
您可能需要考虑在 JavaScript 中实现一个已知的 PRNG,并从
crypto.getRandomValues
中为其注入强随机性。
当您处理 0(含)和 1(不含)之间的值时,请考虑二进制浮点表示的含义。整数部分始终为 0(偶数),小数部分用 2 的倒数和表示:1/2、1/4、1/8、1/16 等。 中的每 1 位尾数代表值中存在 2 的倒数幂之一。
因此,任何随机尾数都将是偶数的倒数,因为所有分母都是偶数。当您将该偶数和除以奇数值 (
Number.MAX_SAFE_INTEGER
) 时,您必须得到奇数结果,因为您无法将偶数乘以偶数并得到奇数结果。
@pointy 很好地强调了解决方案。我只是在这里留下一个工作代码,它带来偶数和奇数的相等比例。
随机数生成的边界值越大,结果为偶数的概率越高。如果将此值加倍,则多次运行后比率将保持相等。
因此,由于 JavaScript 的浮点数表示形式,这里的重点仍然是分数。
Math.floor(
Number.MAX_SAFE_INTEGER * (2 * Math.random())
);
class RandomNumberCounter {
constructor() {
this.evenCount = 0;
this.oddCount = 0;
this.sumMathRandom = 0;
}
generateNumbersAndCount() {
for (let i = 0; i < 10000000; i++) {
// Changed
const mathRandom = 2 * Math.random()
const randomNumber = Math.floor(
Number.MAX_SAFE_INTEGER * (mathRandom)
);
if (randomNumber % 2 === 0) {
this.evenCount++;
} else {
this.oddCount++;
}
this.sumMathRandom += mathRandom
}
}
printCounts() {
console.log("Number of even numbers:", this.evenCount);
console.log("Number of odd numbers:", this.oddCount);
console.log("Avarage of Math.random:", this.sumMathRandom / 10000000);
}
}
const randomNumberCounter = new RandomNumberCounter();
randomNumberCounter.generateNumbersAndCount();
randomNumberCounter.printCounts();