use super::funejson::Value; use super::money_amount::{money, money_from_value, money_to_value, Money}; /// Split `amount` across `ratios` so the parts add back up to the whole. /// /// Splitting 10.00 three ways gives 3.34, 3.33, 3.33 - never 3.33 three times /// with a penny quietly lost. The leftover goes to the largest remainders, /// ties broken by position, so the split is stable and reproducible. /// /// # Panics /// Panics if `ratios` is empty or sums to zero. pub fn allocate(amount: &Money, ratios: &[i64]) -> Vec { if ratios.is_empty() { panic!("allocate needs at least one ratio"); } let total: i64 = ratios.iter().sum(); if total == 0 { panic!("ratios must not sum to zero"); } let mut bases: Vec = Vec::with_capacity(ratios.len()); let mut remainders: Vec = Vec::with_capacity(ratios.len()); let total128 = total as i128; for ratio in ratios { // Euclidean division keeps the remainder non-negative, which is the // invariant the largest-remainder pass below relies on, and matches // floor division in the TypeScript and Python implementations. let numerator = (amount.minor as i128) * (*ratio as i128); let base = numerator.div_euclid(total128); bases.push(base as i64); remainders.push(numerator.rem_euclid(total128) as i64); } let mut leftover = amount.minor - bases.iter().sum::(); let mut order: Vec = (0..ratios.len()).collect(); order.sort_by(|a, b| remainders[*b].cmp(&remainders[*a]).then(a.cmp(b))); for index in order { if leftover <= 0 { break; } bases[index] += 1; leftover -= 1; } bases .iter() .map(|base| money(*base, &amount.currency)) .collect() } pub fn fune_vector(args: &[Value]) -> Value { let ratios: Vec = args[1].as_arr().iter().map(|v| v.as_i64()).collect(); Value::Arr( allocate(&money_from_value(&args[0]), &ratios) .iter() .map(money_to_value) .collect(), ) }