From d742469b42dff0bf4b9f40e11f592275ae35ab62 Mon Sep 17 00:00:00 2001 From: Param Harrison Date: Thu, 24 Sep 2026 14:30:45 +0300 Subject: [PATCH 1/2] factory: claim #61 by runner-69042-1790249445279 From 95a9744607f3da351558040d88b4866c75309331 Mon Sep 17 00:00:00 2001 From: Param Harrison Date: Thu, 24 Sep 2026 14:38:58 +0300 Subject: [PATCH 2/2] factory: build #61 --- src/money/cents.ts | 17 ++++++++--------- tests/money.test.ts | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/money/cents.ts b/src/money/cents.ts index f6321d8..0049c72 100644 --- a/src/money/cents.ts +++ b/src/money/cents.ts @@ -29,22 +29,21 @@ export function parseCents(input: string): number { /** * Split an integer amount of cents evenly across member ids. * - * SEEDED DEFECT (issue #2): this floors every share and drops the - * remainder instead of handing the leftover cents to the first N members, - * so the shares can sum to less than totalCents. `bun test` does not catch - * this because the baseline tests only split amounts that divide evenly. - * The correct remainder rule is documented in + * Applies the remainder rule: every member gets floor(totalCents / n), and + * the first `remainder` members (in the order passed) get one extra cent, so + * the shares always sum to exactly totalCents. See * .claude/skills/handling-money/references/rules.md. */ export function splitCents(totalCents: number, memberIds: string[]): Record { if (memberIds.length === 0) { throw new Error("cannot split among zero members"); } - const share = Math.floor(totalCents / memberIds.length); + const base = Math.floor(totalCents / memberIds.length); + const remainder = totalCents - base * memberIds.length; const shares: Record = {}; - for (const id of memberIds) { - shares[id] = share; - } + memberIds.forEach((id, i) => { + shares[id] = i < remainder ? base + 1 : base; + }); return shares; } diff --git a/tests/money.test.ts b/tests/money.test.ts index ed1ee3f..2c5bbae 100644 --- a/tests/money.test.ts +++ b/tests/money.test.ts @@ -36,4 +36,36 @@ describe("splitCents", () => { const shares = splitCents(2000, ["a", "b"]); expect(sumCents(Object.values(shares))).toBe(2000); }); + + test("gives leftover cents to the first members", () => { + const shares = splitCents(1000, ["a", "b", "c"]); + expect(shares).toEqual({ a: 334, b: 333, c: 333 }); + expect(sumCents(Object.values(shares))).toBe(1000); + }); + + test("shares always sum to the total for uneven splits", () => { + const cases: Array<[number, number]> = [ + [1001, 3], + [1, 3], + [2, 3], + [1000, 7], + [999, 4], + [5, 1], + ]; + for (const [total, n] of cases) { + const ids = Array.from({ length: n }, (_, i) => `m${i}`); + const shares = splitCents(total, ids); + const values = ids.map((id) => shares[id]!); + expect(sumCents(values)).toBe(total); + expect(Math.max(...values) - Math.min(...values)).toBeLessThanOrEqual(1); + // Extra cents go to the leading members, in input order. + const extra = total % n; + const base = Math.floor(total / n); + values.forEach((v, i) => expect(v).toBe(i < extra ? base + 1 : base)); + } + }); + + test("throws when splitting among zero members", () => { + expect(() => splitCents(1000, [])).toThrow("cannot split among zero members"); + }); });