split() loses or invents cents
Done- Plan (done)
- Code (done)
- Test (done)
- Review (done)
- Finish (done)
Cost
$0.0053
Tests
Passed
Iterations
1
Re-plans
0
LLM calls
10
Prompt read from cache
76%
Usage by agent
- Cache read
- Cache write
- Input
- Output
View as table
| Agent | Cost | Cache read | Cache write | Input | Output |
|---|---|---|---|---|---|
| planner | $0.0011 | 5,955 | 869 | 8 | 1,923 |
| coder | $0.0023 | 16,335 | 4,767 | 10 | 3,026 |
| tester | $0.0019 | 13,769 | 5,843 | 8 | 2,107 |
Timeline
-
planner iteration 1 Finished 3 calls · 8,755 tokens · $0.0011 · 12.4s
Loading…
-
coder iteration 1 Finished 4 calls · 24.1K tokens · $0.0023 · 15.3s
Loading…
-
tester iteration 1 Finished 3 calls · 21.7K tokens · $0.0019 · 14.3s
Loading…
-
verify iteration 1 Passed 0.6s
............... [100%] 15 passed in 0.04s
Final diff
diff --git a/money.py b/money.py index 6c4f693..45998b5 100644 --- a/money.py +++ b/money.py @@ -1,3 +1,35 @@ def split(amount_cents: int, weights: list[int]) -> list[int]: + """Split an integer number of cents in proportion to integer weights. + + The shares always sum to exactly ``amount_cents``. Each share starts as + the rounded-down proportional amount, and leftover cents go one at a time + to the largest fractional remainders (ties go to the earlier position). + Negative amounts are split as their absolute value and then negated. + """ + if any(weight < 0 for weight in weights): + raise ValueError("weights must not be negative") total = sum(weights) - return [round(amount_cents * weight / total) for weight in weights] + if total <= 0: + raise ValueError("weights must sum to more than 0") + + sign = -1 if amount_cents < 0 else 1 + n = abs(amount_cents) + + bases = [] + fracs = [] + for weight in weights: + numerator = n * weight + bases.append(numerator // total) + fracs.append(numerator % total) + + leftover = n - sum(bases) + order = sorted(range(len(weights)), key=lambda i: (-fracs[i], i)) + shares = list(bases) + for i in order[:leftover]: + shares[i] += 1 + + for i, weight in enumerate(weights): + if weight == 0: + shares[i] = 0 + + return [sign * share for share in shares] diff --git a/test_money.py b/test_money.py index e667503..f699f68 100644 --- a/test_money.py +++ b/test_money.py @@ -7,3 +7,92 @@ def test_even(): def test_weighted(): assert split(90, [1, 2]) == [30, 60] + + +def test_leftover_cent_goes_to_earlier_position(): + assert split(100, [1, 1, 1]) == [34, 33, 33] + assert split(1, [1, 1]) == [1, 0] + + +def test_negative_amount(): + assert split(-100, [1, 1, 1]) == [-34, -33, -33] + + +def test_zero_amount(): + assert split(0, [1, 2]) == [0, 0] + + +def test_zero_weight_gets_zero(): + assert split(10, [0, 1, 1]) == [0, 5, 5] + + +def test_sum_always_equals_amount(): + import random + + rng = random.Random(1234) + for _ in range(500): + amount = rng.randint(0, 10**9) + weights = [rng.randint(0, 20) for _ in range(rng.randint(1, 8))] + if sum(weights) == 0: + continue + shares = split(amount, weights) + assert sum(shares) == amount + assert all(s == 0 for s, w in zip(shares, weights) if w == 0) + + +def test_no_positive_total_raises(): + import pytest + + with pytest.raises(ValueError): + split(100, []) + with pytest.raises(ValueError): + split(100, [0, 0]) + + +def test_negative_weight_raises(): + import pytest + + with pytest.raises(ValueError): + split(100, [2, -1, 1]) + + +def test_largest_remainder_beats_earlier_position(): + # numerators 2, 4, 4 over total 5 -> bases 0, 0, 0; leftover 2 goes to + # the two largest remainders (indices 1 and 2), not to index 0. + assert split(2, [1, 2, 2]) == [0, 1, 1] + + +def test_three_way_leftover_order(): + assert split(10, [1, 1, 1]) == [4, 3, 3] + + +def test_large_amount_is_exact(): + amount = 2**60 + 1 + shares = split(amount, [1, 2, 3]) + assert sum(shares) == amount + assert all(isinstance(s, int) for s in shares) + + +def test_negative_amount_with_zero_weight(): + assert split(-10, [0, 1, 1]) == [0, -5, -5] + + +def test_negative_sum_always_equals_amount(): + import random + + rng = random.Random(99) + for _ in range(300): + amount = -rng.randint(1, 10**9) + weights = [rng.randint(0, 20) for _ in range(rng.randint(1, 8))] + if sum(weights) == 0: + continue + shares = split(amount, weights) + assert sum(shares) == amount + assert shares == [-s for s in split(-amount, weights)] + + +def test_empty_weights_with_negative_amount_raises(): + import pytest + + with pytest.raises(ValueError): + split(-100, [])