Skip to content

Commit 0af493e

Browse files
krissssclaude
andcommitted
fix: 修复 average() 方法负数精度分配问题
- 重构 ceil 模式算法:从前往后分配,确保不超过剩余总数,最后一个承担差额 - 统一 floor 和 round 模式:差额给最后一个元素 - 修复负数场景下的精度分配错误 - 添加完整的测试用例覆盖正数、负数和不同舍入模式 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent ad3f1ca commit 0af493e

File tree

2 files changed

+88
-3
lines changed

2 files changed

+88
-3
lines changed

src/BCSummary.php

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,63 @@ public function average(float|string|int|null $total, array $proportions): array
2828
if (BCS::create($sum, $operateConfig)->isEqual(0)) {
2929
return array_map(static fn() => 0, $proportions);
3030
}
31+
32+
$keys = array_keys($proportions);
33+
$lastKey = end($keys);
34+
reset($keys);
35+
36+
// ceil 模式:从前往后分配,确保不超过剩余总数,最后一个承担差额
37+
if ($this->config['ceil']) {
38+
$result = [];
39+
$remaining = $total;
40+
$totalPositive = BCS::create($total, $operateConfig)->isLargerThan(0);
41+
42+
foreach ($proportions as $index => $proportion) {
43+
if ($index === $lastKey) {
44+
// 最后一个元素取剩余值
45+
$result[$index] = $this->getScaleNumber($remaining);
46+
} else {
47+
// 计算当前元素的理论值
48+
$value = BCS::create($proportion, $operateConfig)
49+
->div($sum)
50+
->mul($total)
51+
->getResult();
52+
$scaledValue = $this->getScaleNumber($value);
53+
54+
// 确保不超过剩余总数
55+
if ($totalPositive) {
56+
if (BCS::create($scaledValue, $operateConfig)->isLargerThan($remaining)) {
57+
$scaledValue = $this->getScaleNumber($remaining);
58+
}
59+
} else {
60+
if (BCS::create($scaledValue, $operateConfig)->isLessThan($remaining)) {
61+
$scaledValue = $this->getScaleNumber($remaining);
62+
}
63+
}
64+
65+
$result[$index] = $scaledValue;
66+
$remaining = BCS::create($remaining, $operateConfig)->sub($scaledValue)->getResult();
67+
}
68+
}
69+
return $result;
70+
}
71+
72+
// floor 和 round 模式:差额给最后一个元素
3173
$result = [];
32-
$lastOne = array_slice($proportions, count($proportions) - 1, 1, true);
33-
array_pop($proportions);
3474
foreach ($proportions as $index => $proportion) {
75+
if ($index === $lastKey) {
76+
continue;
77+
}
3578
$result[$index] = BCS::create($proportion, $this->config)
3679
->div($sum)
3780
->mul($total)
3881
->getResult();
3982
}
83+
84+
// 最后一个元素 = total - 前面所有元素的和
4085
$sumBefore = BC::create($this->config)->add(...array_values($result));
41-
$result[key($lastOne)] = BCS::create($total, $this->config)->sub($sumBefore)->getResult();
86+
$result[$lastKey] = BCS::create($total, $this->config)->sub($sumBefore)->getResult();
87+
4288
return $result;
4389
}
4490

tests/IssueTest.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,45 @@
22

33
use kriss\bcmath\BC;
44
use kriss\bcmath\BCS;
5+
use kriss\bcmath\BCSummary;
6+
7+
test('issue-1', function () {
8+
$result = BCSummary::create(['scale' => 0])->average(1, ['A' => 1, 'B' => 1, 'C' => 1]);
9+
expect($result)->toEqual(['A' => 0, 'B' => 0, 'C' => 1]);
10+
11+
$result = BCSummary::create(['scale' => 0, 'ceil' => true])->average(1, ['A' => 1, 'B' => 1, 'C' => 1]);
12+
expect($result)->toEqual(['A' => 1, 'B' => 0, 'C' => 0]);
13+
14+
$result = BCSummary::create(['scale' => 0])->average(2, ['A' => 1, 'B' => 1, 'C' => 1]);
15+
expect($result)->toEqual(['A' => 1, 'B' => 1, 'C' => 0]);
16+
17+
$result = BCSummary::create(['scale' => 0, 'floor' => true])->average(2, ['A' => 1, 'B' => 1, 'C' => 1]);
18+
expect($result)->toEqual(['A' => 0, 'B' => 0, 'C' => 2]);
19+
20+
$result = BCSummary::create(['scale' => 0])->average(3, ['A' => 1, 'B' => 1, 'C' => 1]);
21+
expect($result)->toEqual(['A' => 1, 'B' => 1, 'C' => 1]);
22+
23+
$result = BCSummary::create(['scale' => 0])->average(4, ['A' => 1, 'B' => 1, 'C' => 1]);
24+
expect($result)->toEqual(['A' => 1, 'B' => 1, 'C' => 2]);
25+
26+
$result = BCSummary::create(['scale' => 0])->average(4, ['A' => 1, 'B' => 2, 'C' => 1]);
27+
expect($result)->toEqual(['A' => 1, 'B' => 2, 'C' => 1]);
28+
29+
$result = BCSummary::create(['scale' => 0])->average(5, ['A' => 1, 'B' => 2, 'C' => 1]);
30+
expect($result)->toEqual(['A' => 1, 'B' => 3, 'C' => 1]);
31+
32+
$result = BCSummary::create(['scale' => 0])->average(-1, ['A' => 1, 'B' => 1, 'C' => 1]);
33+
expect($result)->toEqual(['A' => 0, 'B' => 0, 'C' => -1]);
34+
35+
$result = BCSummary::create(['scale' => 0])->average(-2, ['A' => 1, 'B' => 1, 'C' => 1]);
36+
expect($result)->toEqual(['A' => -1, 'B' => -1, 'C' => 0]);
37+
38+
$result = BCSummary::create(['scale' => 0])->average(-3, ['A' => 1, 'B' => 1, 'C' => 1]);
39+
expect($result)->toEqual(['A' => -1, 'B' => -1, 'C' => -1]);
40+
41+
$result = BCSummary::create(['scale' => 0])->average(-4, ['A' => 1, 'B' => 1, 'C' => 1]);
42+
expect($result)->toEqual(['A' => -1, 'B' => -1, 'C' => -2]);
43+
});
544

645
test('issue-2', function () {
746
$total = BCS::create(0, ['scale' => 6]);

0 commit comments

Comments
 (0)