为什么乘以 MAX_SAFE_INTEGER 生成的随机整数在奇数和偶数之间分布不均匀?

问题描述 投票:0回答:3

尝试使用 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);

javascript
3个回答
7
投票

首先,您应该乘以 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
中为其注入强随机性。


3
投票

当您处理 0(含)和 1(不含)之间的值时,请考虑二进制浮点表示的含义。整数部分始终为 0(偶数),小数部分用 2 的倒数和表示:1/2、1/4、1/8、1/16 等。 中的每 1 位尾数代表值中存在 2 的倒数幂之一。

因此,任何随机尾数都将是偶数的倒数,因为所有分母都是偶数。当您将该偶数和除以奇数值 (

Number.MAX_SAFE_INTEGER
) 时,您必须得到奇数结果,因为您无法将偶数乘以偶数并得到奇数结果。


3
投票

@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();

© www.soinside.com 2019 - 2024. All rights reserved.