diff --git a/fps-core/src/evaluator.rs b/fps-core/src/evaluator.rs index 3a697a1..2b5364b 100644 --- a/fps-core/src/evaluator.rs +++ b/fps-core/src/evaluator.rs @@ -68,6 +68,10 @@ pub fn evaluate(expr: &Expr, max_degree: usize) -> Result { let series = evaluate(inner, max_degree)?; series.sin() } + Expr::Cos(inner) => { + let series = evaluate(inner, max_degree)?; + series.cos() + } Expr::Exp(inner) => { let series = evaluate(inner, max_degree)?; series.exp() diff --git a/fps-core/src/parser.rs b/fps-core/src/parser.rs index 9bbec2f..4d68a50 100644 --- a/fps-core/src/parser.rs +++ b/fps-core/src/parser.rs @@ -13,6 +13,7 @@ pub enum Expr { Pow(Box, Box), Neg(Box), Sin(Box), + Cos(Box), Exp(Box), Log(Box), } @@ -59,6 +60,7 @@ impl<'a> Parser<'a> { match self.consume() { Some(Token::RParen) => match func { FunctionToken::Sin => Ok(Expr::Sin(Box::new(expr))), + FunctionToken::Cos => Ok(Expr::Cos(Box::new(expr))), FunctionToken::Exp => Ok(Expr::Exp(Box::new(expr))), FunctionToken::Log => Ok(Expr::Log(Box::new(expr))), }, diff --git a/fps-core/src/series.rs b/fps-core/src/series.rs index 10c2dba..33744f2 100644 --- a/fps-core/src/series.rs +++ b/fps-core/src/series.rs @@ -190,6 +190,44 @@ impl Series { Ok(result) } + pub fn cos(&self) -> Result { + if !self.constant_term().is_zero() { + return Err(EvalError::FunctionRequiresZeroConstant("cos")); + } + + let max_degree = self.max_degree(); + let mut result = Series::zero(max_degree); + + if max_degree == 0 { + result.coeffs[0] = BigRational::one(); + return Ok(result); + } + + let mut factorial = BigInt::one(); + for n in 0..=(max_degree / 2) { + if n > 0 { + let two_n = 2 * (n as i64); + factorial *= BigInt::from(two_n - 1); + factorial *= BigInt::from(two_n); + } + + let power = if n == 0 { + Series::one(max_degree) + } else { + self.powi((2 * n) as i64)? + }; + let sign = if n % 2 == 0 { + BigInt::one() + } else { + -BigInt::one() + }; + let coeff = BigRational::new(sign, factorial.clone()); + result = result.add(&power.scale(&coeff)); + } + + Ok(result) + } + pub fn exp(&self) -> Result { if !self.constant_term().is_zero() { return Err(EvalError::FunctionRequiresZeroConstant("exp")); diff --git a/fps-core/src/tokenizer.rs b/fps-core/src/tokenizer.rs index cf579c8..17d7ba0 100644 --- a/fps-core/src/tokenizer.rs +++ b/fps-core/src/tokenizer.rs @@ -4,6 +4,7 @@ use thiserror::Error; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FunctionToken { Sin, + Cos, Exp, Log, } @@ -62,6 +63,7 @@ pub fn tokenize(input: &str) -> Result, TokenizerError> { let token = match ident.as_str() { "x" => Token::Variable('x'), "sin" => Token::Function(FunctionToken::Sin), + "cos" => Token::Function(FunctionToken::Cos), "exp" => Token::Function(FunctionToken::Exp), "log" => Token::Function(FunctionToken::Log), _ => return Err(TokenizerError::UnexpectedIdentifier(ident)), diff --git a/tests/power_series.rs b/tests/power_series.rs index 42335e7..9e4ece1 100644 --- a/tests/power_series.rs +++ b/tests/power_series.rs @@ -19,6 +19,12 @@ fn sin_series_matches_expected_output() { assert_eq!(formatted, "3 x - 9/2 x^3 + O(x^4)"); } +#[test] +fn cos_series_matches_expected_output() { + let formatted = evaluate("cos(5x)", 4); + assert_eq!(formatted, "1 - 25/2 x^2 + 625/24 x^4 + O(x^5)"); +} + #[test] fn exp_series_matches_expected_output() { let formatted = evaluate("exp(5x)", 3);