1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| <?php
class RedPacket
{
/**
* @param float|string $rmb 红包总金额(元)
* @param int|string $num 红包数量
* @param int|string $kind 0 随机,1 平均
* @param float|string $max 单份上限,0 自动计算
* @return array|false
*/
public static function create($rmb, $num, $kind = 0, $max = 0)
{
if (!is_numeric($rmb) || !is_numeric($num) || !is_numeric($max)
|| $num < 1 || $num > PHP_INT_MAX || $num != (int) $num) {
return false;
}
$rmb = round((float) $rmb, 2);
$num = (int) $num;
$max = (float) $max;
if (!is_finite($rmb) || !is_finite($max)
|| $rmb <= 0 || $rmb >= PHP_INT_MAX / 100) {
return false;
}
if ($num < 2) $kind = 1;
$min = 0.01;
$packets = [];
if ($min * $num > $rmb + 1e-9) {
return false;
}
switch ($kind) {
case 1:
// 以分处理平均分配尾差,保证总金额不变。
$total = (int) round($rmb * 100);
$base = (int) floor($total / $num);
$extra = $total % $num;
for ($i = 0; $i < $num; $i++) {
$packets[] = sprintf('%.2f', ($base + ($i < $extra ? 1 : 0)) / 100);
}
return $packets;
default:
// 上限不满足分配条件时自动回退。
if ($num * $max < $rmb - 1e-9 || $max + $num * $min > $rmb + 1e-9) {
$max = $rmb / 3;
}
$max = floor($max * 100 + 1e-9) / 100;
// 两份红包或分币尾差可能使三分之一上限不可行。
$requiredMax = ceil(round($rmb * 100) / $num) / 100;
$max = max($max, $requiredMax);
while ($num >= 1) {
$num--;
$kMin = round(max($min, $rmb - $num * $max), 2);
$kMax = round(min($max, $rmb - $num * $min), 2);
$kAvg = $rmb / ($num + 1);
$kDis = max(0, min($kAvg - $kMin, $kMax - $kAvg));
$r = ((float) (rand(1, 10000) / 10000) - 0.5) * $kDis * 2;
$k = $num === 0 ? $rmb : min($kMax, max($kMin, round($kAvg + $r, 2)));
$rmb = round($rmb - $k, 2);
$packets[] = sprintf('%.2f', $k);
}
shuffle($packets);
return $packets;
}
}
}
|