diff --git a/Cargo.toml b/Cargo.toml index 354897c..f660041 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ dirs = "6.0.0" num = "0.4.1" num-bigint = "0.4.4" num-traits = "0.2.18" +num-rational = "0.4" bigdecimal = "0.4.2" statrs = "0.18.0" diff --git a/src/rpn_resolver.rs b/src/rpn_resolver.rs index f2eb505..47fb384 100644 --- a/src/rpn_resolver.rs +++ b/src/rpn_resolver.rs @@ -13,6 +13,7 @@ use std::{ }; use num::{BigInt, BigUint, One, Zero}; +use num_rational::BigRational; use num_traits::ToPrimitive; static MALFORMED_ERR: &str = "Runtime Error: The mathematical expression is malformed."; @@ -101,7 +102,10 @@ impl RpnResolver<'_> { if right_value == zero { return Err(anyhow!(DIVISION_ZERO_ERR)); } - left_value = Number::DecimalNumber(left_value.into()); + left_value = Number::DecimalNumber( + BigRational::from_float(f64::from(left_value)) + .expect("valid float"), + ); result_stack.push_back(left_value / right_value); var_stack.push_back(None); } @@ -110,7 +114,10 @@ impl RpnResolver<'_> { if left_value == zero { return Err(anyhow!(DIVISION_ZERO_ERR)); } - left_value = Number::DecimalNumber(left_value.into()); + left_value = Number::DecimalNumber( + BigRational::from_float(f64::from(left_value)) + .expect("valid float"), + ); } result_stack.push_back(left_value ^ right_value); var_stack.push_back(None); @@ -157,8 +164,11 @@ impl RpnResolver<'_> { let var_name = v.to_lowercase(); debug!("Heap {:?}", self.local_heap); let heap = self.local_heap.borrow(); - let n = heap.get(&var_name).unwrap_or(&Number::DecimalNumber(0.)); - result_stack.push_back(n.clone()); + let n = heap + .get(&var_name) + .cloned() + .unwrap_or_else(|| Number::DecimalNumber(BigRational::from_integer(BigInt::zero()))); + result_stack.push_back(n); var_stack.push_back(Some(var_name)); } Token::Function(fun) => { @@ -212,7 +222,9 @@ impl RpnResolver<'_> { MathFunction::Exp => f64::exp(value.into()), MathFunction::None => return Err(anyhow!("This should never happen!")), }; - result_stack.push_back(Number::DecimalNumber(res)); + result_stack.push_back(Number::DecimalNumber( + BigRational::from_float(res).expect("valid float"), + )); var_stack.push_back(None); } Token::SemiColon => { @@ -452,12 +464,21 @@ mod tests { fn test_max_min() { let session = Session::init(); let mut resolver = session.process("max(1,2)"); - assert_eq!(resolver.resolve().unwrap(), Number::DecimalNumber(2.0)); + assert_eq!( + resolver.resolve().unwrap(), + Number::DecimalNumber(BigRational::from_float(2.0).unwrap()) + ); let mut resolver = session.process("min(1,2)"); - assert_eq!(resolver.resolve().unwrap(), Number::DecimalNumber(1.0)); + assert_eq!( + resolver.resolve().unwrap(), + Number::DecimalNumber(BigRational::from_float(1.0).unwrap()) + ); let mut resolver = session.process("min(max(1,2),3)"); - assert_eq!(resolver.resolve().unwrap(), Number::DecimalNumber(2.0)); + assert_eq!( + resolver.resolve().unwrap(), + Number::DecimalNumber(BigRational::from_float(2.0).unwrap()) + ); } } diff --git a/src/session.rs b/src/session.rs index 54ff437..35a4ff0 100644 --- a/src/session.rs +++ b/src/session.rs @@ -46,20 +46,31 @@ impl Session { let mut local_heap: HashMap = HashMap::new(); local_heap.insert( "pi".to_string(), - Number::DecimalNumber(std::f64::consts::PI), + Number::DecimalNumber( + num_rational::BigRational::from_float(std::f64::consts::PI).unwrap(), + ), + ); + local_heap.insert( + "e".to_string(), + Number::DecimalNumber(num_rational::BigRational::from_float(std::f64::consts::E).unwrap()), ); - local_heap.insert("e".to_string(), Number::DecimalNumber(std::f64::consts::E)); local_heap.insert( "tau".to_string(), - Number::DecimalNumber(std::f64::consts::TAU), + Number::DecimalNumber( + num_rational::BigRational::from_float(std::f64::consts::TAU).unwrap(), + ), ); local_heap.insert( "phi".to_string(), - Number::DecimalNumber((1.0 + 5.0f64.sqrt()) / 2.0), + Number::DecimalNumber( + num_rational::BigRational::from_float((1.0 + 5.0f64.sqrt()) / 2.0).unwrap(), + ), ); local_heap.insert( "gamma".to_string(), - Number::DecimalNumber(0.577_215_664_901_532_9_f64), + Number::DecimalNumber( + num_rational::BigRational::from_float(0.577_215_664_901_532_9_f64).unwrap(), + ), ); local_heap } @@ -88,7 +99,10 @@ impl Session { pub fn setf(&self, key: &str, value: f64) { self.variable_heap .borrow_mut() - .insert(key.to_lowercase(), Number::DecimalNumber(value)); + .insert( + key.to_lowercase(), + Number::DecimalNumber(num_rational::BigRational::from_float(value).unwrap()), + ); } } @@ -102,7 +116,10 @@ mod tests { fn test_session() { let session = Session::init(); let mut resolver: RpnResolver = session.process("1+2*3/(4-5)"); - assert_eq!(resolver.resolve().unwrap(), Number::DecimalNumber(-5.0)); + assert_eq!( + resolver.resolve().unwrap(), + Number::DecimalNumber(num_rational::BigRational::from_float(-5.0).unwrap()) + ); } /// Test for setting an integer variable @@ -111,7 +128,10 @@ mod tests { let session = Session::init(); session.set("x", 4); let mut resolver: RpnResolver = session.process("x+2*3/(4-5)"); - assert_eq!(resolver.resolve().unwrap(), Number::DecimalNumber(-2.0)); + assert_eq!( + resolver.resolve().unwrap(), + Number::DecimalNumber(num_rational::BigRational::from_float(-2.0).unwrap()) + ); } /// Test for setting a float variable @@ -120,7 +140,10 @@ mod tests { let session = Session::init(); session.setf("x", 4.5); let mut resolver: RpnResolver = session.process("x+2*3/(4-5)"); - assert_eq!(resolver.resolve().unwrap(), Number::DecimalNumber(-1.5)); + assert_eq!( + resolver.resolve().unwrap(), + Number::DecimalNumber(num_rational::BigRational::from_float(-1.5).unwrap()) + ); } /// Test for the default variables initialization @@ -130,7 +153,10 @@ mod tests { let mut resolver: RpnResolver = session.process("pi + e"); assert_eq!( resolver.resolve().unwrap(), - Number::DecimalNumber(std::f64::consts::PI + std::f64::consts::E) + Number::DecimalNumber( + num_rational::BigRational::from_float(std::f64::consts::PI).unwrap() + + num_rational::BigRational::from_float(std::f64::consts::E).unwrap() + ) ); } @@ -141,7 +167,9 @@ mod tests { let mut resolver: RpnResolver = session.process("tau / 2"); assert_eq!( resolver.resolve().unwrap(), - Number::DecimalNumber(std::f64::consts::TAU / 2.0) + Number::DecimalNumber( + num_rational::BigRational::from_float(std::f64::consts::TAU / 2.0).unwrap(), + ) ); } } diff --git a/src/token.rs b/src/token.rs index 1d265eb..1bcae0f 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,4 +1,5 @@ -use bigdecimal::ToPrimitive; +use num_traits::ToPrimitive; +use num_rational::BigRational; use log::debug; use num_bigint::BigInt; use num_traits::FromPrimitive; @@ -8,14 +9,14 @@ use std::{ }; /// Enum Type [Number]. Either an BigInt integer [`Number::NaturalNumber`] -/// or a f64 float [`Number::DecimalNumber`] +/// or a [`BigRational`] rational number [`Number::DecimalNumber`] /// #[derive(Debug, PartialEq, Clone)] pub enum Number { /// an Integer [BigInt] NaturalNumber(BigInt), - /// a Float [f64] - DecimalNumber(f64), + /// a Rational number [BigRational] + DecimalNumber(BigRational), } /// A binary or unary Math [`Operator`] @@ -221,7 +222,9 @@ impl Token<'_> { } if let Ok(v) = t.parse::() { - return Some(Token::Operand(Number::DecimalNumber(v))); + if let Some(r) = BigRational::from_float(v) { + return Some(Token::Operand(Number::DecimalNumber(r))); + } } if let Some(fun) = Token::get_some(t) { @@ -268,7 +271,10 @@ impl Display for Number { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Number::NaturalNumber(v) => write!(f, "{v}"), - Number::DecimalNumber(v) => write!(f, "{v}"), + Number::DecimalNumber(v) => { + let fl = v.to_f64().expect("Should not happen"); + write!(f, "{fl}") + } } } } @@ -287,14 +293,17 @@ impl Display for Number { fn apply_functional_token_operation(ln: Number, rn: Number, nf: NF, df: DF) -> Number where NF: Fn(BigInt, BigInt) -> BigInt, - DF: Fn(f64, f64) -> f64, + DF: Fn(BigRational, BigRational) -> BigRational, { match (ln, rn.clone()) { (Number::NaturalNumber(v1), Number::NaturalNumber(v2)) => Number::NaturalNumber(nf(v1, v2)), (Number::NaturalNumber(v1), Number::DecimalNumber(v2)) => { - Number::DecimalNumber(df(ToPrimitive::to_f64(&v1).expect("Should not happen"), v2)) + Number::DecimalNumber(df(BigRational::from(v1), v2)) + } + (Number::DecimalNumber(v1), Number::NaturalNumber(v2)) => { + Number::DecimalNumber(df(v1, BigRational::from(v2))) } - (Number::DecimalNumber(v1), _) => Number::DecimalNumber(df(v1, rn.into())), + (Number::DecimalNumber(v1), Number::DecimalNumber(v2)) => Number::DecimalNumber(df(v1, v2)), } } @@ -339,7 +348,11 @@ impl BitXor for Number { self, rhs, |a, b| BigInt::pow(&a, b.try_into().unwrap()), - f64::powf, + |a, b| { + let af = a.to_f64().expect("Should not happen"); + let bf = b.to_f64().expect("Should not happen"); + BigRational::from_float(f64::powf(af, bf)).expect("Should not happen") + }, ) } } @@ -350,11 +363,11 @@ impl PartialOrd for Number { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { (Number::NaturalNumber(v1), Number::NaturalNumber(v2)) => v1.partial_cmp(&v2), - (Number::NaturalNumber(v1), Number::DecimalNumber(v2)) => ToPrimitive::to_f64(v1) - .expect("Should not happen") - .partial_cmp(v2), + (Number::NaturalNumber(v1), Number::DecimalNumber(v2)) => { + BigRational::from(v1.clone()).partial_cmp(v2) + } (Number::DecimalNumber(v1), Number::NaturalNumber(v2)) => { - v1.partial_cmp(&(ToPrimitive::to_f64(v2).expect("Should not happen"))) + v1.partial_cmp(&BigRational::from(v2.clone())) } (Number::DecimalNumber(v1), Number::DecimalNumber(v2)) => v1.partial_cmp(&v2), } @@ -365,7 +378,7 @@ impl From for f64 { fn from(n: Number) -> f64 { match n { Number::NaturalNumber(v) => ToPrimitive::to_f64(&v).expect("Should not happen"), - Number::DecimalNumber(v) => v, + Number::DecimalNumber(v) => v.to_f64().expect("Should not happen"), } } } @@ -375,7 +388,9 @@ impl From for BigInt { fn from(n: Number) -> BigInt { match n { Number::NaturalNumber(v) => v, - Number::DecimalNumber(v) => BigInt::from_f64(v).expect("Should not happen"), + Number::DecimalNumber(v) => { + BigInt::from_f64(v.to_f64().expect("Should not happen")).expect("Should not happen") + } } } } @@ -384,7 +399,7 @@ impl From for i32 { fn from(n: Number) -> i32 { match n { Number::NaturalNumber(v) => ToPrimitive::to_i32(&v).expect("Should not happen"), - Number::DecimalNumber(v) => ToPrimitive::to_i32(&v).expect("Should not happen"), // not good + Number::DecimalNumber(v) => ToPrimitive::to_i32(&BigInt::from_f64(v.to_f64().expect("Should not happen")).expect("Should not happen")).expect("Should not happen"), } } } @@ -393,7 +408,7 @@ impl From for i64 { fn from(n: Number) -> i64 { match n { Number::NaturalNumber(v) => ToPrimitive::to_i64(&v).expect("Should not happen"), - Number::DecimalNumber(v) => ToPrimitive::to_i64(&v).expect("Should not happen"), // not good + Number::DecimalNumber(v) => ToPrimitive::to_i64(&BigInt::from_f64(v.to_f64().expect("Should not happen")).expect("Should not happen")).expect("Should not happen"), } } } @@ -402,7 +417,7 @@ impl From for i128 { fn from(n: Number) -> i128 { match n { Number::NaturalNumber(v) => ToPrimitive::to_i128(&v).expect("Should not happen"), - Number::DecimalNumber(v) => ToPrimitive::to_i128(&v).expect("Should not happen"), // not good + Number::DecimalNumber(v) => ToPrimitive::to_i128(&BigInt::from_f64(v.to_f64().expect("Should not happen")).expect("Should not happen")).expect("Should not happen"), } } } @@ -467,7 +482,9 @@ mod tests { ); assert_eq!( Token::tokenize(v[2]), - Some(Token::Operand(Number::DecimalNumber(2.1))) + Some(Token::Operand(Number::DecimalNumber( + BigRational::from_float(2.1).unwrap() + ))) ); } @@ -511,7 +528,9 @@ mod tests { ); assert_eq!( Token::tokenize("3.14"), - Some(Token::Operand(Number::DecimalNumber(3.14))) + Some(Token::Operand(Number::DecimalNumber( + BigRational::from_float(3.14).unwrap() + ))) ); assert_eq!(Token::tokenize("("), Some(Token::Bracket(Bracket::Open))); } @@ -525,7 +544,9 @@ mod tests { ); assert_eq!( Token::tokenize("3.14"), - Some(Token::Operand(Number::DecimalNumber(3.14))) + Some(Token::Operand(Number::DecimalNumber( + BigRational::from_float(3.14).unwrap() + ))) ); assert_eq!(Token::tokenize("("), Some(Token::Bracket(Bracket::Open))); } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index c6e721c..61fc663 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -16,7 +16,12 @@ macro_rules! resolve { macro_rules! resolve_decimal { ($expr:expr, $expected:expr) => {{ - resolve!($expr, Number::DecimalNumber($expected)); + let session = Session::init(); + let mut resolver = session.process($expr); + let result = resolver.resolve().unwrap(); + assert!(matches!(result, Number::DecimalNumber(_))); + let res_f: f64 = result.clone().into(); + assert!((res_f - $expected).abs() < 1e-10); }}; () => { panic!("Expected a decimal number, but got an invalid result."); @@ -200,7 +205,10 @@ fn test_session_set() { let session = Session::init(); session.set("x", 4); let mut resolver: RpnResolver = session.process("x+2*3/(4-5)"); - assert_eq!(resolver.resolve().unwrap(), Number::DecimalNumber(-2.0)); + assert_eq!( + resolver.resolve().unwrap(), + Number::DecimalNumber(num_rational::BigRational::from_float(-2.0).unwrap()) + ); } #[test]