Skip to main content

kharcha_core/
split.rs

1/// Splits `total_paisa` across `count` people so the parts sum EXACTLY to total.
2/// Port of `lib/core/bill_splitter.dart` `splitBillPaisa`.
3/// `div_euclid`/`rem_euclid` match Dart `~/`/`%` on non-negative totals;
4/// on negatives Rust keeps the exact sum where Dart drifts (improvement,
5/// real inputs are non-negative). Counts past 10k return empty (FFI OOM guard).
6pub fn split_bill_paisa(total_paisa: i64, count: usize) -> Vec<i64> {
7    // Audit: unclamped count is an OOM vector via FFI (`split_bill(u64::MAX)`).
8    // 10k people is absurd for a bill; refuse beyond it.
9    if count == 0 || count > 10_000 {
10        return vec![];
11    }
12    if count == 1 {
13        return vec![total_paisa];
14    }
15    let n = count as i64;
16    let base = total_paisa.div_euclid(n);
17    let rem = total_paisa.rem_euclid(n);
18    (0..n).map(|i| base + i64::from(i < rem)).collect()
19}
20
21#[cfg(test)]
22mod tests {
23    use super::*;
24
25    #[test]
26    fn splits_evenly() {
27        assert_eq!(split_bill_paisa(3000, 3), vec![1000, 1000, 1000]);
28    }
29
30    #[test]
31    fn remainder_spread_to_first_people() {
32        assert_eq!(split_bill_paisa(100, 3), vec![34, 33, 33]);
33        assert_eq!(split_bill_paisa(1, 2), vec![1, 0]);
34    }
35
36    #[test]
37    fn parts_always_sum_exactly() {
38        for (total, count) in [(12345, 7), (999, 2), (5000, 1), (1, 20)] {
39            let parts = split_bill_paisa(total, count);
40            assert_eq!(parts.iter().sum::<i64>(), total);
41        }
42    }
43}