Skip to content

Commit

Permalink
Merge pull request #23 from vic1707/factorials
Browse files Browse the repository at this point in the history
Factorials
  • Loading branch information
vic1707 authored Nov 30, 2023
2 parents f819c4c + bd9211d commit 21c6c84
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 9 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,9 @@ And parsing errors are implemented using the [`miette`](https://crates.io/crates
Xprs supports the following operations:

- Binary operations: `+`, `-`, `*`, `/`, `^`, `%`.
- Unary operations: `+`, `-`. <!--, `!` -->
- Unary operations: `+`, `-`, `!`.

Note: `!` (factorial) is only supported on positive integers. Calling it on a negative integer or a float will result in `f64::NAN`. Also `-4!` is interpreted as `-(4!)` and not `(-4)!`.

#### Built-in constants

Expand All @@ -225,7 +227,7 @@ Xprs supports a variety of functions:
- logarithmic functions: `ln` (base 2), `log` (base 10), `logn` (base n, used as `logn(num, base)`).
- power functions: `sqrt`, `cbrt`, `exp`.
- rounding functions: `floor`, `ceil`, `round`, `trunc`.
- other functions: `abs`, `min`, `max`, `hypot`, `fract`, `recip` (`invert` alias), `sum`, `mean`.
- other functions: `abs`, `min`, `max`, `hypot`, `fract`, `recip` (`invert` alias), `sum`, `mean`, `factorial` and `gamma`.

Note: `min` and `max` can take any number of arguments (if none, returns `f64::INFINITY` and `-f64::INFINITY` respectively).
Note2: `sum` and `mean` can take any number of arguments (if none, returns `0` and `f64::NAN` respectively).
Expand Down
3 changes: 3 additions & 0 deletions src/element/binop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ impl fmt::Display for BinOp<'_> {
}

impl PartialEq for BinOp<'_> {
#[allow(clippy::unreachable)]
fn eq(&self, other: &Self) -> bool {
self.op == other.op
&& match self.op {
Expand All @@ -52,6 +53,8 @@ impl PartialEq for BinOp<'_> {
(self.lhs == other.lhs && self.rhs == other.rhs)
|| (self.lhs == other.rhs && self.rhs == other.lhs)
},
// not a binary operator
Operator::Factorial => unreachable!(),
}
}
}
12 changes: 11 additions & 1 deletion src/element/simplify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use crate::{
element::{BinOp, Element, FunctionCall, UnOp},
token::Operator,
utils::factorial::factorial,
};

/// Trait for simplifying abstract syntax tree (AST) elements.
Expand Down Expand Up @@ -45,7 +46,7 @@ impl<'a> Simplify<'a> for BinOp<'a> {
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
fn simplify(mut self) -> Element<'a> {
use Element::Number;
use Operator::{Divide, Minus, Modulo, Plus, Power, Times};
use Operator::{Divide, Factorial, Minus, Modulo, Plus, Power, Times};
self.lhs = self.lhs.simplify();
self.rhs = self.rhs.simplify();
match self {
Expand Down Expand Up @@ -222,13 +223,15 @@ impl<'a> Simplify<'a> for BinOp<'a> {
rhs: Number(rhs),
lhs: Number(lhs),
} => {
#[allow(clippy::unreachable)]
let result = match op {
Plus => lhs + rhs,
Minus => lhs - rhs,
Times => lhs * rhs,
Divide => lhs / rhs,
Power => lhs.powf(rhs),
Modulo => lhs % rhs,
Factorial => unreachable!(),
};
Number(result)
},
Expand All @@ -248,6 +251,13 @@ impl<'a> Simplify<'a> for UnOp<'a> {
#[allow(clippy::unreachable)]
match self.op {
Operator::Plus => self.operand,
Operator::Factorial => match self.operand {
Element::Number(num) => Element::Number(factorial(num)),
Element::UnOp(_)
| Element::BinOp(_)
| Element::Function(_)
| Element::Variable(_) => self.into(),
},
Operator::Minus => match self.operand {
Element::Number(num) => Element::Number(-num),
Element::UnOp(_)
Expand Down
8 changes: 5 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,9 @@
//! Xprs supports the following operations:
//!
//! - Binary operations: `+`, `-`, `*`, `/`, `^`, `%`.
//! - Unary operations: `+`, `-`. <!--, `!` -->
//! - Unary operations: `+`, `-`, `!`.
//!
//! Note: `!` (factorial) is only supported on positive integers. Calling it on a negative integer or a float will result in `f64::NAN`. Also `-4!` is interpreted as `-(4!)` and not `(-4)!`.
//!
//! #### Built-in constants
//!
Expand All @@ -225,7 +227,7 @@
//! - logarithmic functions: `ln` (base 2), `log` (base 10), `logn` (base n, used as `logn(num, base)`).
//! - power functions: `sqrt`, `cbrt`, `exp`.
//! - rounding functions: `floor`, `ceil`, `round`, `trunc`.
//! - other functions: `abs`, `min`, `max`, `hypot`, `fract`, `recip` (`invert` alias), `sum`, `mean`.
//! - other functions: `abs`, `min`, `max`, `hypot`, `fract`, `recip` (`invert` alias), `sum`, `mean`, `factorial` and `gamma`.
//!
//! Note: `min` and `max` can take any number of arguments (if none, returns `f64::INFINITY` and `-f64::INFINITY` respectively).
//! Note2: `sum` and `mean` can take any number of arguments (if none, returns `0` and `f64::NAN` respectively).
Expand Down Expand Up @@ -303,7 +305,7 @@

/* NIGHTLY Features */
// box-patterns if nightly
#![cfg_attr(NIGHTLY, feature(box_patterns))]
#![cfg_attr(NIGHTLY, feature(box_patterns, float_gamma))]
/* Clippy config */
#![allow(clippy::pub_use, clippy::needless_doctest_main)]
/* Modules */
Expand Down
6 changes: 6 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ impl<'input, 'ctx> ParserImpl<'input, 'ctx> {
) -> Result<Element<'input>, ParseError> {
let mut el = self.atom()?;

// Right-associative unary operators
if self.consume_if_eq(b'!') {
el = UnOp::new_element(Operator::Factorial, el);
}

// BinOp with higher precedence
while let Some((op, op_precedence)) =
self.get_operator_infos(&el, precedence)
{
Expand Down
25 changes: 25 additions & 0 deletions src/tests/factorial.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* Crate imports */
use super::macros::assert_f64_eq;
use crate::utils::factorial::factorial;

#[test]
fn test_float_factorial() {
// negatives
assert!(factorial(-f64::INFINITY).is_nan());
assert!(factorial(-5.0).is_nan());
assert!(factorial(-2.5).is_nan());
assert!(factorial(-1.0).is_nan());
assert_f64_eq!(factorial(-0.0), 1.0);
// positives
assert_f64_eq!(factorial(0.0), 1.0);
assert_f64_eq!(factorial(1.0), 1.0);
assert!(factorial(2.5).is_nan());
assert_f64_eq!(factorial(2.0), 2.0);
assert_f64_eq!(factorial(5.0), 120.0);
// approaching the limit
assert_f64_eq!(factorial(169.0), 4.269_068_009_004_702_7e304);
assert_f64_eq!(factorial(170.0), 7.257_415_615_307_994e306);
// at the limit
assert!(factorial(171.0).is_infinite());
assert!(factorial(f64::INFINITY).is_infinite());
}
1 change: 1 addition & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* Modules */
mod factorial;
mod issues;
mod macros;
mod parser;
Expand Down
2 changes: 2 additions & 0 deletions src/token/identifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ impl<'a> Identifier<'a> {
"max" => built_in_functions::MAX.into(),
"hypot" => built_in_functions::HYPOT.into(),
"fract" => built_in_functions::FRACT.into(),
"gamma" => built_in_functions::GAMMA.into(),
"factorial" => built_in_functions::FACTORIAL.into(),
/* Variables */
_ => Identifier::Variable(value),
}
Expand Down
6 changes: 4 additions & 2 deletions src/token/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub enum Operator {
Power,
/// Modulo operator.
Modulo,
/// Factorial operator.
Factorial,
}

impl TryFrom<u8> for Operator {
Expand All @@ -26,7 +28,7 @@ impl TryFrom<u8> for Operator {
/// Valid operators are: '+', '-', '*', '/', '^', '%'.
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
// b'!' => Ok(Self::Factorial),
b'!' => Ok(Self::Factorial),
b'+' => Ok(Self::Plus),
b'-' => Ok(Self::Minus),
b'*' => Ok(Self::Times),
Expand All @@ -41,7 +43,7 @@ impl TryFrom<u8> for Operator {
impl fmt::Display for Operator {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
// Self::Factorial => write!(f, "!"),
Self::Factorial => write!(fmt, "!"),
Self::Plus => write!(fmt, "+"),
Self::Minus => write!(fmt, "-"),
Self::Times => write!(fmt, "*"),
Expand Down
7 changes: 7 additions & 0 deletions src/utils/built_in_functions.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* Crate imports */
use super::factorial::{factorial, gamma};
use crate::{token::Function, xprs_fn};

/// Sine builtin function.
Expand Down Expand Up @@ -100,3 +101,9 @@ pub const HYPOT: Function = xprs_fn!("hypot", f64::hypot, 2);

/// Fractional part of a number.
pub const FRACT: Function = xprs_fn!("fract", f64::fract, 1);

/// Factorial builtin function.
pub const FACTORIAL: Function = xprs_fn!("factorial", factorial, 1);

/// Gamma builtin function.
pub const GAMMA: Function = xprs_fn!("gamma", gamma, 1);
Loading

0 comments on commit 21c6c84

Please sign in to comment.