重新映射到新范围后保持均匀分布

Keep uniform distribution after remapping to a new range

因为这是关于将一​​个均匀分布重新映射到另一个具有不同范围的分布,所以这不是一个 PHP 问题,尽管我使用的是 PHP。

我有一个加密安全的随机数生成器,它为我提供 0PHP_INT_MAX 之间均匀分布的整数(均匀离散分布)。

如何重新映射这些结果以有效地适应不同的范围?

目前我正在使用 $mappedRandomNumber = $randomNumber % ($range + 1) + $min,其中 $range = $max - $min,但显然不起作用,因为范围内的第一个 PHP_INT_MAX%$range 整数有更高的机会被选中,打破了分布的均匀性。

好吧,对 PHP 的了解为零,我绝对有资格成为专家,所以

在心理上转换为浮点数 U[0,1)

f = r / PHP_MAX_INT

然后做

mapped = min + f*(max - min)

回到整数

mapped = min + (r * max - r * min)/PHP_MAX_INT

如果计算是通过 64 位数学完成的,并且 PHP_MAX_INT 是 2^31 它应该可以工作

这就是我最终所做的。 PRNG 101(如果不合适,忽略并重新生成)。不是很复杂,但很简单:

public function rand($min = 0, $max = null){

  // pow(2,$numBits-1) calculated as (pow(2,$numBits-2)-1) + pow(2,$numBits-2) 
  // to avoid overflow when $numBits is the number of bits of PHP_INT_MAX
  $maxSafe = (int) floor(
    ((pow(2,8*$this->intByteCount-2)-1) + pow(2,8*$this->intByteCount-2))   
    / 
    ($max - $min)
  ) * ($max - $min);

  // discards anything above the last interval N * {0 .. max - min -1} 
  // that fits in {0 ..  2^(intBitCount-1)-1}
  do {
    $chars = $this->getRandomBytesString($this->intByteCount);
    $n = 0;
    for ($i=0;$i<$this->intByteCount;$i++) {$n|=(ord($chars[$i])<<(8*($this->intByteCount-$i-1)));}
  } while (abs($n)>$maxSafe);

  return (abs($n)%($max-$min+1))+$min;

}

欢迎任何改进。

(完整代码在 https://github.com/elcodedocle/cryptosecureprng/blob/master/CryptoSecurePRNG.php

这是我将如何做的草图:

假设您在 [A, B) 范围内有均匀的随机整数分布,这就是您的随机数生成器所提供的。 让L = B - A。 设 P 为 2 的最高次方,使得 P <= L。 设 X 为该范围内的样本。 首先计算Y = X - A。 如果 Y >= P,丢弃它并从新的 X 开始,直到你得到一个适合的 Y

现在 Y 包含 log2(P) 个均匀随机位 - 零扩展到 log2(P) 个位。

现在我们有了统一的随机位生成器,可用于根据需要提供任意数量的随机位。

要生成目标范围内的数字,让 [A_t, B_t) 成为目标范围。让L_t = B_t - A_t。 令 P_t 为满足 P_t >= L_t 的最小 2 次方。 读取 log2(P_t) 个随机位并从中生成一个整数,我们称之为 X_t。 如果 X_t >= L_t,丢弃它并重试,直到你得到一个合适的数字。 您在所需范围内的随机数将为 L_t + A_t.

实施注意事项:如果您的 L_tL 是 2 的幂,您永远不必丢弃任何东西。如果不是,那么即使在最坏的情况下,您也应该在平均不到 2 次试验中得到正确的数字。