//! Variadic list operations: min, max, gcd, lcm. //! //! All accept 1 or more arguments. `gcd` and `lcm` require integer arguments. use super::{FunctionError, FunctionRegistry}; fn min_fn(args: &[f64]) -> Result { // We already know args.len() >= 1 from the variadic guard. let mut m = args[0]; for &v in &args[1..] { if v < m { m = v; } } Ok(m) } fn max_fn(args: &[f64]) -> Result { let mut m = args[0]; for &v in &args[1..] { if v > m { m = v; } } Ok(m) } /// GCD of two non-negative integers using Euclidean algorithm. fn gcd_pair(mut a: i64, mut b: i64) -> i64 { a = a.abs(); b = b.abs(); while b != 0 { let t = b; b = a % b; a = t; } a } /// LCM of two non-negative integers. fn lcm_pair(a: i64, b: i64) -> i64 { if a == 0 || b == 0 { return 0; } (a.abs() / gcd_pair(a, b)) * b.abs() } fn gcd_fn(args: &[f64]) -> Result { for &v in args { if v.fract() != 0.0 { return Err(FunctionError::new("gcd requires integer arguments")); } } let mut result = args[0] as i64; for &v in &args[1..] { result = gcd_pair(result, v as i64); } Ok(result as f64) } fn lcm_fn(args: &[f64]) -> Result { for &v in args { if v.fract() != 0.0 { return Err(FunctionError::new("lcm requires integer arguments")); } } let mut result = args[0] as i64; for &v in &args[1..] { result = lcm_pair(result, v as i64); } Ok(result as f64) } /// Register list-operation functions. pub fn register(reg: &mut FunctionRegistry) { reg.register_variadic("min", 1, min_fn); reg.register_variadic("max", 1, max_fn); reg.register_variadic("gcd", 1, gcd_fn); reg.register_variadic("lcm", 1, lcm_fn); } #[cfg(test)] mod tests { use super::*; fn reg() -> FunctionRegistry { FunctionRegistry::new() } // --- min --- #[test] fn min_single() { let v = reg().call("min", &[42.0]).unwrap(); assert!((v - 42.0).abs() < 1e-10); } #[test] fn min_two() { let v = reg().call("min", &[3.0, 7.0]).unwrap(); assert!((v - 3.0).abs() < 1e-10); } #[test] fn min_many() { let v = reg().call("min", &[10.0, 3.0, 7.0, 1.0, 5.0]).unwrap(); assert!((v - 1.0).abs() < 1e-10); } #[test] fn min_negative() { let v = reg().call("min", &[-5.0, -2.0, -10.0]).unwrap(); assert!((v - (-10.0)).abs() < 1e-10); } #[test] fn min_no_args_error() { let err = reg().call("min", &[]).unwrap_err(); assert!(err.message.contains("at least 1")); } // --- max --- #[test] fn max_single() { let v = reg().call("max", &[42.0]).unwrap(); assert!((v - 42.0).abs() < 1e-10); } #[test] fn max_two() { let v = reg().call("max", &[3.0, 7.0]).unwrap(); assert!((v - 7.0).abs() < 1e-10); } #[test] fn max_many() { let v = reg().call("max", &[10.0, 3.0, 7.0, 1.0, 50.0]).unwrap(); assert!((v - 50.0).abs() < 1e-10); } #[test] fn max_no_args_error() { let err = reg().call("max", &[]).unwrap_err(); assert!(err.message.contains("at least 1")); } // --- gcd --- #[test] fn gcd_two_numbers() { let v = reg().call("gcd", &[12.0, 8.0]).unwrap(); assert!((v - 4.0).abs() < 1e-10); } #[test] fn gcd_three_numbers() { let v = reg().call("gcd", &[12.0, 8.0, 6.0]).unwrap(); assert!((v - 2.0).abs() < 1e-10); } #[test] fn gcd_coprime() { let v = reg().call("gcd", &[7.0, 13.0]).unwrap(); assert!((v - 1.0).abs() < 1e-10); } #[test] fn gcd_with_zero() { let v = reg().call("gcd", &[0.0, 5.0]).unwrap(); assert!((v - 5.0).abs() < 1e-10); } #[test] fn gcd_single() { let v = reg().call("gcd", &[42.0]).unwrap(); assert!((v - 42.0).abs() < 1e-10); } #[test] fn gcd_non_integer_error() { let err = reg().call("gcd", &[3.5, 2.0]).unwrap_err(); assert!(err.message.contains("integer")); } // --- lcm --- #[test] fn lcm_two_numbers() { let v = reg().call("lcm", &[4.0, 6.0]).unwrap(); assert!((v - 12.0).abs() < 1e-10); } #[test] fn lcm_three_numbers() { let v = reg().call("lcm", &[4.0, 6.0, 10.0]).unwrap(); assert!((v - 60.0).abs() < 1e-10); } #[test] fn lcm_with_zero() { let v = reg().call("lcm", &[0.0, 5.0]).unwrap(); assert!((v - 0.0).abs() < 1e-10); } #[test] fn lcm_single() { let v = reg().call("lcm", &[42.0]).unwrap(); assert!((v - 42.0).abs() < 1e-10); } #[test] fn lcm_coprime() { let v = reg().call("lcm", &[7.0, 13.0]).unwrap(); assert!((v - 91.0).abs() < 1e-10); } #[test] fn lcm_non_integer_error() { let err = reg().call("lcm", &[3.5, 2.0]).unwrap_err(); assert!(err.message.contains("integer")); } }