//! Logarithmic, exponential, and root functions. //! //! - `ln` — natural logarithm (base e) //! - `log` — common logarithm (base 10) //! - `log2` — binary logarithm (base 2) //! - `exp` — e raised to a power //! - `pow` — base raised to an exponent (2 args) //! - `sqrt` — square root //! - `cbrt` — cube root use super::{FunctionError, FunctionRegistry}; fn ln_fn(args: &[f64]) -> Result { let x = args[0]; if x <= 0.0 { return Err(FunctionError::new( "Argument out of domain for ln (must be positive)", )); } Ok(x.ln()) } fn log_fn(args: &[f64]) -> Result { let x = args[0]; if x <= 0.0 { return Err(FunctionError::new( "Argument out of domain for log (must be positive)", )); } Ok(x.log10()) } fn log2_fn(args: &[f64]) -> Result { let x = args[0]; if x <= 0.0 { return Err(FunctionError::new( "Argument out of domain for log2 (must be positive)", )); } Ok(x.log2()) } fn exp_fn(args: &[f64]) -> Result { Ok(args[0].exp()) } fn pow_fn(args: &[f64]) -> Result { Ok(args[0].powf(args[1])) } fn sqrt_fn(args: &[f64]) -> Result { let x = args[0]; if x < 0.0 { return Err(FunctionError::new( "Argument out of domain for sqrt (must be non-negative)", )); } Ok(x.sqrt()) } fn cbrt_fn(args: &[f64]) -> Result { Ok(args[0].cbrt()) } /// Register all logarithmic/exponential/root functions. pub fn register(reg: &mut FunctionRegistry) { reg.register_fixed("ln", 1, ln_fn); reg.register_fixed("log", 1, log_fn); reg.register_fixed("log2", 1, log2_fn); reg.register_fixed("exp", 1, exp_fn); reg.register_fixed("pow", 2, pow_fn); reg.register_fixed("sqrt", 1, sqrt_fn); reg.register_fixed("cbrt", 1, cbrt_fn); } #[cfg(test)] mod tests { use super::*; fn reg() -> FunctionRegistry { FunctionRegistry::new() } // --- ln --- #[test] fn ln_one_is_zero() { let v = reg().call("ln", &[1.0]).unwrap(); assert!(v.abs() < 1e-10); } #[test] fn ln_e_is_one() { let v = reg().call("ln", &[std::f64::consts::E]).unwrap(); assert!((v - 1.0).abs() < 1e-10); } #[test] fn ln_zero_domain_error() { let err = reg().call("ln", &[0.0]).unwrap_err(); assert!(err.message.contains("out of domain")); } #[test] fn ln_negative_domain_error() { let err = reg().call("ln", &[-1.0]).unwrap_err(); assert!(err.message.contains("out of domain")); } // --- log (base 10) --- #[test] fn log_100_is_2() { let v = reg().call("log", &[100.0]).unwrap(); assert!((v - 2.0).abs() < 1e-10); } #[test] fn log_1000_is_3() { let v = reg().call("log", &[1000.0]).unwrap(); assert!((v - 3.0).abs() < 1e-10); } #[test] fn log_negative_domain_error() { let err = reg().call("log", &[-1.0]).unwrap_err(); assert!(err.message.contains("out of domain")); } // --- log2 --- #[test] fn log2_256_is_8() { let v = reg().call("log2", &[256.0]).unwrap(); assert!((v - 8.0).abs() < 1e-10); } #[test] fn log2_one_is_zero() { let v = reg().call("log2", &[1.0]).unwrap(); assert!(v.abs() < 1e-10); } #[test] fn log2_negative_domain_error() { let err = reg().call("log2", &[-5.0]).unwrap_err(); assert!(err.message.contains("out of domain")); } // --- exp --- #[test] fn exp_zero_is_one() { let v = reg().call("exp", &[0.0]).unwrap(); assert!((v - 1.0).abs() < 1e-10); } #[test] fn exp_one_is_e() { let v = reg().call("exp", &[1.0]).unwrap(); assert!((v - std::f64::consts::E).abs() < 1e-10); } // --- pow --- #[test] fn pow_2_10_is_1024() { let v = reg().call("pow", &[2.0, 10.0]).unwrap(); assert!((v - 1024.0).abs() < 1e-10); } #[test] fn pow_3_0_is_1() { let v = reg().call("pow", &[3.0, 0.0]).unwrap(); assert!((v - 1.0).abs() < 1e-10); } // --- sqrt --- #[test] fn sqrt_144_is_12() { let v = reg().call("sqrt", &[144.0]).unwrap(); assert!((v - 12.0).abs() < 1e-10); } #[test] fn sqrt_2_approx() { let v = reg().call("sqrt", &[2.0]).unwrap(); assert!((v - std::f64::consts::SQRT_2).abs() < 1e-10); } #[test] fn sqrt_zero_is_zero() { let v = reg().call("sqrt", &[0.0]).unwrap(); assert!(v.abs() < 1e-10); } #[test] fn sqrt_negative_domain_error() { let err = reg().call("sqrt", &[-4.0]).unwrap_err(); assert!(err.message.contains("out of domain")); } // --- cbrt --- #[test] fn cbrt_27_is_3() { let v = reg().call("cbrt", &[27.0]).unwrap(); assert!((v - 3.0).abs() < 1e-10); } #[test] fn cbrt_8_is_2() { let v = reg().call("cbrt", &[8.0]).unwrap(); assert!((v - 2.0).abs() < 1e-10); } #[test] fn cbrt_neg_8_is_neg_2() { let v = reg().call("cbrt", &[-8.0]).unwrap(); assert!((v - (-2.0)).abs() < 1e-10); } // --- composition --- #[test] fn ln_exp_roundtrip() { let r = reg(); let inner = r.call("exp", &[5.0]).unwrap(); let v = r.call("ln", &[inner]).unwrap(); assert!((v - 5.0).abs() < 1e-10); } #[test] fn exp_ln_roundtrip() { let r = reg(); let inner = r.call("ln", &[10.0]).unwrap(); let v = r.call("exp", &[inner]).unwrap(); assert!((v - 10.0).abs() < 1e-10); } #[test] fn sqrt_pow_roundtrip() { let r = reg(); let inner = r.call("pow", &[3.0, 2.0]).unwrap(); let v = r.call("sqrt", &[inner]).unwrap(); assert!((v - 3.0).abs() < 1e-10); } }