PHP - 改进随机数序列生成

PHP - Improve random number sequence generation

我想这是我关于 SO 的第一个问题。

我目前正在网站上工作,我必须为彩票生成 1 到 29 之间的 6 个号码(每个号码各一个)。因为它们可以是任意顺序的,所以我只是简单地对它们进行排序。

如果我没记错的话,这应该意味着有 (29*28*27*26*25*24) / 6! = 475020 种不同的可能组合。

我尝试了不同的生成序列的方法,使用 mt_rand 或 random_int(来自 random_compat)但是当我用类似 10k 次迭代的方式测试它时,我总是获得大约 100 个重复项,即使它们仍然有 465k 个组合可用。

以下是我一直在尝试的代码示例:

// Using an array and mt_rand (or random_int, giving same results)
// Also tried shuffling the array instead of simply reindexing it, not better

$values = range(1, 29);

while(count($values) > 6) {
    unset($values[mt_rand(0, count($values) - 1)]);
    $values = array_values($values);
}



// Creating the array from random numbers (same results using random_int)

$values = array();
while (count($values) < 6) {
    $r = mt_rand(1, 29);
    if (in_array($r, $values)) {
        continue;
    } else {
        $values[] = $r;
    }
}

很好...我的问题是:

谢谢!

琳.

PS : 翻了很多问题,没有找到满足我需求的东西,如果我看起来不够好,请原谅!

只是为了说明一些事情: 使用 random_int(使用 /dev/urandom 或 openssl_random_pseudo_bytes)没有改善任何我认为会的。如果可能的话,我不想使用任何外部 API(比如 random.org)。

要改进 "randomness",您可以尝试加密库,例如phpseclib.

他们的数学库中有一个 random() 函数 here

编辑:计算机生成的数字不能随机。使用密码库可以获得最好的 伪随机 结果,最简单、最随机的解决方案是 @Matthias Leuffen 之一。

rand() 和 mt_rand() 依靠纯数学来产生伪随机数。

要获得真正的随机数,您可以使用 http://www.random.org

的网络服务

假设安装了正确的扩展,您可以使用 openssl_random_pseudo_bytes()

示例:

function strong_random() {
    return hexdec(bin2hex(openssl_random_pseudo_bytes(20)));
}

注意:由于 openssl_random_pseudo_bytes() 的实施,此功能将 非常慢

当然,又快又脏,可以使用添加最大长度参数。

了解 Birthday Paradox

根据我的计算 (bc calculator),在 812 个序列的情况下,29 个项目中有 6 个得到重复组合的概率为 50% 或更高。

define p(n, k) { return (n-k)/n; }
n=475020
m=1; for (k=0; k<811; k++) m *= p(n, k); m
.500649663424
m=1; for (k=0; k<812; k++) m *= p(n, k); m
.499794905988

Using random_int (which makes use of /dev/urandom or openssl_random_pseudo_bytes) doesn't improve anything, which I thought would.

当然可以,只是视觉上无法识别。 mt_rand()rand() 只允许大约 232 个可能的种子和 232 个可能的输出,最重要的是,有 确定性序列: if you know a few outputs, you can predict the rest until it's reseeded.

您的操作系统的 CSPRNG 没有任何此类限制。知道一些 random_int() 输出(在 PHP 中,在 32 位系统上限制为 232 个可能值,264 在 64 位系统上)不会给你任何关于未来输出的信息。

I'm currently working on a website and I have to generate 6 numbers between 1 and 29 (one of each max) for a lottery. As they can be in any order, I simply sort them afterwards.

好的,这是个好主意。你肯定想要一个 CSPRNG 在这里。

when I test it with something like 10k iterations, I always get around 100 duplicates, even though they are like 465k combinations still available.

正如其他人所指出的,这就是 birthday problem/paradox 在起作用。

如果您需要解决方案,请尝试以下操作:

function random_unique_select($num, array $possible_values)
{
    $sizeof = count($possible_values);
    if ($num > $sizeof) {
        throw new InvalidArgumentException('$num is too large');
    }
    $selected = [];
    for ($i = 0; $i < $num; ++$i) {
        // Grab a random int [0, ... N - 1]
        $r = random_int(0, $sizeof - 1);
        // Copy the selected value into $selected
        $selected[] = $possible_values[$r];
        // Delete it from the range of possible values
        unset($possible_values[$r]);
        // N has grown smaller by 1
        --$sizeof;
        // Reset keys; we want this to be zero-indexed.
        $possible_values = array_values($possible_values);
    }
    return $selected;
}

$lottery = random_unique_select(6, range(1,29));

演示: