Compare commits

...

3 commits

Author SHA1 Message Date
b101b5af51 Fix them rounding errors! 2025-09-06 16:07:09 +12:00
86c6c96909 Checkpoint 2 (seems to compile now) 2025-09-06 15:36:22 +12:00
cd493f8d32 Checkpoint 2025-09-06 15:08:40 +12:00

View file

@ -1,5 +1,15 @@
use tracing::{debug, error};
use once_cell::sync::Lazy;
use astro_float::{BigFloat, Sign, RoundingMode, Consts};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::mem;
pub const PRECISION: usize = 2048;
static CONST_CACHE: Lazy<Arc<Mutex<Consts>>> = Lazy::new(|| Arc::new(Mutex::new(Consts::new().expect("Unable to allocate memory for Conts cache"))));
// static PI: Lazy<BigFloat> = Lazy::new(|| BigFloat::from_str("3.141592653589793238462643383279").unwrap());
// static E: Lazy<BigFloat> = Lazy::new(|| BigFloat::from_str("2.718281828459045235360287471352").unwrap());
#[derive(Debug)]
pub struct Calculation {
@ -10,21 +20,22 @@ pub struct Calculation {
pub fn calculate(query: &str) -> Option<Calculation> {
debug!("Got query {}", query);
let mut parser = Parser::new(Lexer::new(query));
let tree = parser.parse()?;
let mut tree = parser.parse()?;
let res = tree.eval();
let res_float = f64::from_str(&format!("{}", res)).unwrap();
debug!("Calculation: {}", query);
debug!("Tree: {:?}", tree);
debug!("Result: {}", res);
Some(Calculation {equation: query.to_string(), result: res.to_string()})
debug!("Result: {:?}", res_float);
Some(Calculation {equation: query.to_string(), result: res_float.to_string()})
}
// TODO: put into own crate with dependency astro-float = "0.9.2" so I can use more than f64
#[derive(Debug, Copy, Clone, PartialEq)]
// TODO: put into own crate with dependency astro-float = "0.9.2" so I can use more than BigFloat
#[derive(Debug, Clone, PartialEq)]
enum Token {
Op(Op),
Atom(Atom),
/* Number(f64),
/* Number(BigFloat),
Func(Func),*/
}
@ -64,67 +75,93 @@ impl Op {
}
}
fn apply_to(&self, args: &Vec<Expr>) -> f64 {
fn apply_to(&self, args: &mut Vec<Expr>) -> BigFloat {
match args.len() {
1 => match self {
Op::Subtract => Some(0.0 - args[0].eval()),
Op::Add => Some(args[0].eval().abs()),
Op::Func(f) => match f {
Func::Sine => Some(args[0].eval().sin()),
Func::Cosine => Some(args[0].eval().cos()),
Func::Tangent => Some(args[0].eval().tan()),
Func::ArcSine => Some(args[0].eval().asin()),
Func::ArcCosine => Some(args[0].eval().acos()),
Func::ArcTangent => Some(args[0].eval().atan()),
Func::Log2 => Some(args[0].eval().log2()),
Func::Log10 => Some(args[0].eval().log10()),
Func::LogN => Some(args[0].eval().ln()),
Func::Square => Some(args[0].eval().powf(2.0)),
Func::SquareRoot => Some(args[0].eval().sqrt()),
Func::Abs => Some(args[0].eval().abs()),
Func::Log => None,
// _ => todo!("{:?}", self)
Op::Subtract => {
let mut res = args[0].eval();
res.set_sign(Sign::Neg);
res
},
Op::Add => {
let mut res = args[0].eval();
res.set_sign(Sign::Pos);
res
}
_ => None,
Op::Func(f) => match f {
Func::Sine => args[0].eval().sin(PRECISION, RoundingMode::None, &mut CONST_CACHE.lock().unwrap()),
Func::Cosine => args[0].eval().cos(PRECISION, RoundingMode::None, &mut CONST_CACHE.lock().unwrap()),
Func::Tangent => args[0].eval().tan(PRECISION, RoundingMode::None, &mut CONST_CACHE.lock().unwrap()),
Func::ArcSine => args[0].eval().asin(PRECISION, RoundingMode::None, &mut CONST_CACHE.lock().unwrap()),
Func::ArcCosine => args[0].eval().acos(PRECISION, RoundingMode::None, &mut CONST_CACHE.lock().unwrap()),
Func::ArcTangent => args[0].eval().atan(PRECISION, RoundingMode::None, &mut CONST_CACHE.lock().unwrap()),
Func::Log2 => args[0].eval().log2(PRECISION, RoundingMode::None, &mut CONST_CACHE.lock().unwrap()),
Func::Log10 => args[0].eval().log10(PRECISION, RoundingMode::None, &mut CONST_CACHE.lock().unwrap()),
Func::LogN => args[0].eval().ln(PRECISION, RoundingMode::None, &mut CONST_CACHE.lock().unwrap()),
Func::Square => args[0].eval().pow(&BigFloat::from_f64(2.0, PRECISION), PRECISION, RoundingMode::None, &mut CONST_CACHE.lock().unwrap()),
Func::SquareRoot => args[0].eval().sqrt(PRECISION, RoundingMode::None),
Func::Abs => args[0].eval().abs(),
_ => {
error!("Got 1 params for func {:?} which expects 2 (should not be possible)", self);
astro_float::NAN
},
},
_ => {
error!("Got 1 params for {:?} which expects 2 (should not be possible)", self);
astro_float::NAN
},
}
2 => match self {
Op::LParen => Some(args[0].eval()),
Op::RParen => Some(args[0].eval()),
Op::Add => Some(args[0].eval() + args[1].eval()),
Op::Subtract => Some(args[0].eval() - args[1].eval()),
Op::Multiply => Some(args[0].eval() * args[1].eval()),
Op::Divide => Some(args[0].eval() / args[1].eval()),
Op::Exponent => Some(args[0].eval().powf(args[1].eval())),
Op::Func(Func::Log) => Some(args[0].eval().log(args[1].eval())),
_ => None,
Op::LParen => args[0].eval(),
Op::RParen => args[0].eval(),
Op::Add => args[0].eval().add(&mut args[1].eval(), PRECISION, RoundingMode::None),
Op::Subtract => args[0].eval().sub(&mut args[1].eval(), PRECISION, RoundingMode::None),
Op::Multiply => args[0].eval().mul(&mut args[1].eval(), PRECISION, RoundingMode::None),
Op::Divide => args[0].eval().div(&mut args[1].eval(), PRECISION, RoundingMode::None),
Op::Exponent => args[0].eval().pow(&mut args[1].eval(), PRECISION, RoundingMode::None, &mut CONST_CACHE.lock().unwrap()),
Op::Func(Func::Log) => args[0].eval().log(&mut args[1].eval(), PRECISION, RoundingMode::None, &mut CONST_CACHE.lock().unwrap()),
_ => {
error!("Got 2 params for {:?} which only expects 1 (should not be possible)", self);
astro_float::NAN
},
}
_ => None,
}.unwrap_or_else(|| {
error!("ERROR when evaluating maths expression, got invalid number of args ({}) for {:?}", args.len(), self);
// None
f64::NAN
})
_ => {
error!("Unexpected number of params ({}) for {:?} (should not be possible)", args.len(), self);
astro_float::NAN
},
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
enum Atom {
Number(f64), // TODO: use the high precision floats library instead
Number(BigFloat),
Const(Const),
}
impl Atom {
fn get_val(&self) -> f64 {
/*impl Atom {
fn get_val(&self) -> BigFloat {
match self {
Atom::Number(val) => *val,
Atom::Const(c) => match c {
Const::Pi => 3.141592653589793238462643383279,
Const::E => 2.718281828459045235360287471352,
Const::Inf => f64::INFINITY,
Const::Nan => f64::NAN,
Const::Pi => CONST_CACHE.lock().unwrap().pi(PRECISION, RoundingMode::None),
Const::E => CONST_CACHE.lock().unwrap().e(PRECISION, RoundingMode::None),
Const::Inf => astro_float::INF_POS,
Const::Nan => astro_float::NAN,
}
}
}
}*/
impl Const {
fn get_val(&self) -> BigFloat {
match self {
Const::Pi => CONST_CACHE.lock().unwrap().pi(PRECISION, RoundingMode::None),
Const::E => CONST_CACHE.lock().unwrap().e(PRECISION, RoundingMode::None),
Const::Inf => astro_float::INF_POS,
Const::Nan => astro_float::NAN,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
@ -270,13 +307,14 @@ impl Lexer<'_> {
}
fn next(&mut self) -> Result<Token, ParseErr> {
let val = self.next_tok;
self.next_tok = self._next();
let res = self._next();
let val = mem::replace(&mut self.next_tok, res);
// self.next_tok = self._next();
val
}
fn peek(&mut self) -> Result<Token, ParseErr> {
self.next_tok
fn peek(&mut self) -> &Result<Token, ParseErr> {
&self.next_tok
}
// TODO: replace with iterator so I can do parser.parse(lexer.iter()) and parse does lex_iter.next() & such
@ -348,14 +386,14 @@ impl Parser<'_> {
debug!("loop start");
let op: Op = match self.lex.peek() {
Err(ParseErr::Eof) => break,
Err(e) => { debug!("In expr got err {:?}", e); Err(e) },
Err(e) => { debug!("In expr got err {:?}", e); Err(*e) },
Ok(tok) => match tok {
Token::Op(op) => match op {
Op::RParen => {
debug!("got RParen");
break;
},
_ => Ok(op),
_ => Ok(*op),
}
v => { debug!("Got unexpected token {:?}", v); Err(ParseErr::Invalid) },
}
@ -378,15 +416,31 @@ impl Parser<'_> {
#[derive(Debug)]
enum Expr {
Evaluated,
Atom(Atom),
Node(Op, Vec<Expr>),
}
impl Expr {
fn eval(&self) -> f64 {
let res = match (self) {
Expr::Atom(at) => at.get_val(),
Expr::Node(op, exprs) => op.apply_to(exprs),
fn eval(&mut self) -> BigFloat {
let res = match self {
Expr::Atom(_) => {
let v = mem::replace(self, Expr::Evaluated);
if let Expr::Atom(at) = v {
match at {
Atom::Number(n) => n,
Atom::Const(c) => c.get_val(),
}
} else {
unreachable!();
}
// at.get_val()
}
Expr::Node(op, exprs) => {
*self = Expr::Atom(Atom::Number(op.apply_to(exprs)));
self.eval()
}
Expr::Evaluated => unreachable!("Tried to evaluate an already evaluated node"),
};
// debug!("{:?} evaluated to {}", self, res);
res