在抢红包这个场景中,大都使用的是修正后的随机算法,从统计学意义上来讲要达到先抢后抢得到的红包是差不多的。
微信红包算法的核心是:每次随机的基础是剩余金额除以剩余个数得到的平均值的两倍。
class Test
{
public $remainSize = 20;
public $remainMoney = '10.00';
public $min = '0.01';
public $scale = 2;
public function getRandomMoney(): string
{
if ($this->remainSize <= 0 || bccomp($this->remainMoney, "0") < 1) {
return "0.00";
}
bcscale($this->scale);
if ($this->remainSize == 1) {
$this->remainSize = 0;
return $this->remainMoney;
}
// 核心算法 start
$max = bcmul(bcdiv($this->remainMoney, strval($this->remainSize)), "2");
$money = bcmul(rand(0, 100) / 100, $max);
// 核心算法 end
if (bccomp($money, $this->min) < 1) {
$money = $this->min;
}
$this->remainSize--;
$this->remainMoney = bcsub($this->remainMoney, $money);
return $money;
}
}
测试代码:
function run()
{
$o = new Test();
for ($i = 0; $i < 30; $i++) {
echo $o->getRandomMoney(), PHP_EOL;
}
}
运行两次后得到的结果为:
可见,其波动性还是挺大的,并且也没有证据表示先抢还是后抢会占优势。
分别运行 2000 次和 3000 次看看统计规律
function run2()
{
$sum = [];
bcscale(2);
for ($k = 0; $k < 2000; $k++) {
$o = new Test();
for ($i = 0; $i < 30; $i++) {
$t = $o->getRandomMoney();
if (isset($sum[$i])) {
$sum[$i] = bcadd($sum[$i], $t);
} else {
$sum[$i] = $t;
}
}
}
foreach ($sum as $v) {
echo $v, PHP_EOL;
}
}
2000 次
3000次
可见次数越多越趋近于平均值 1000 元。而这个值正好是 2000 * 10 / 20
。
如果将 2 改成 1.5 ,将会是前低后高的趋势
如果将 2 改成 3 ,将会是前高后低的趋势