implemented trig functions and refactored math helpers

This commit is contained in:
Valerie Wolfe 2024-08-29 13:38:27 -04:00
parent 27abd81bf8
commit 6a74756835
5 changed files with 145 additions and 114 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "quickmath" name = "quickmath"
version = "0.3.0" version = "0.3.1"
edition = "2021" edition = "2021"
authors = [ "Valerie Wolfe <sleeplessval@gmail.com>" ] authors = [ "Valerie Wolfe <sleeplessval@gmail.com>" ]
description = "A quick command-line math evaluator." description = "A quick command-line math evaluator."

View file

@ -10,71 +10,95 @@ pub type EvalResult = Result<Value, EvalexprError>;
// Mathematics // Mathematics
pub fn fix(arg: &Value) -> EvalResult { pub fn cosine(arg: &Value) -> EvalResult {
let args = arg.as_tuple()?; Ok(
if let Value::Float(float) = arg { float.clone() }
let count = args.len(); else if let Value::Int(int) = arg { int.clone() as f64 }
if count != 2 { else { return Err(EvalexprError::expected_number(arg.clone())) }
return Err(EvalexprError::WrongFunctionArgumentAmount { expected: 2..=2, actual: count }); .cos().into()
)
} }
let float = args[0].as_float()?; pub fn fix(arg: &Value) -> EvalResult {
let figures = args[1].as_int()?; if let Value::Tuple(args) = arg {
let len = args.len();
if len == 2 {
let value =
if let Value::Float(float) = args[0] { float.clone() }
else { return Err(EvalexprError::expected_float(args[0].clone())); };
let operand: f64 = i64::pow(10, figures as u32) as f64; let operand = 10u64.pow(
let output = f64::round(float * operand) / operand; if let Value::Int(int) = args[1] { int.clone() }
return Ok(output.into()); else { return Err(EvalexprError::expected_int(args[1].clone())); }
as u32
) as f64;
Ok( ((value * operand).round() / operand).into() )
} else { Err(EvalexprError::wrong_function_argument_amount(len, 2)) }
} else { Err(EvalexprError::wrong_function_argument_amount(1, 2)) }
} }
pub fn logarithm(arg: &Value) -> EvalResult { pub fn logarithm(arg: &Value) -> EvalResult {
let arguments: Vec<Value>; let value: f64;
let count: usize; let base: Option<f64>;
if arg.is_tuple() { match arg {
arguments = arg.as_tuple()?; Value::Tuple(tuple)
count = arguments.len(); => {
} else if arg.is_float() { let len = tuple.len();
arguments = vec!(arg.as_float()?.into()); if len != 2 { return Err(EvalexprError::WrongOperatorArgumentAmount { expected: 2, actual: len }) }
count = 1;
} else if arg.is_int() { let i_value = tuple.get(0).unwrap();
arguments = vec!((arg.as_int()? as f64).into()); if let Value::Float(float) = i_value { value = float.clone(); }
count = 1; else if let Value::Int(int) = i_value { value = int.clone() as f64; }
} else { else { return Err(EvalexprError::expected_number(i_value.clone())); }
return Err(EvalexprError::CustomMessage("Expected numbers".to_string()));
let i_base = tuple.get(1).unwrap();
if let Value::Float(float) = i_value { base = Some(float.clone()); }
else if let Value::Int(int) = i_value { base = Some(int.clone() as f64); }
else { return Err(EvalexprError::expected_number(i_base.clone())); }
},
Value::Float(float)
=> {
value = float.clone();
base = None;
},
Value::Int(int)
=> {
value = int.clone() as f64;
base = None;
}
_ => return Err(EvalexprError::CustomMessage("Expected numbers.".to_owned()))
} }
let output: Value; if let Some(base) = base { Ok(value.log(base).into()) }
match count { else { Ok(value.ln().into()) }
1 => {
let argument = &arguments[0];
if !argument.is_number() {
return Err(EvalexprError::CustomMessage("Expected number".to_string()));
} }
let number = if argument.is_float() { argument.as_float()? } else { argument.as_int()? as f64 };
output = number.ln().into(); pub fn sine(arg: &Value) -> EvalResult {
}, Ok(
2 => { if let Value::Float(float) = arg { float.clone() }
let arg_value = &arguments[0]; else if let Value::Int(int) = arg { int.clone() as f64 }
let arg_base = &arguments[1]; else { return Err(EvalexprError::expected_number(arg.clone())) }
if !(arg_value.is_number() && arg_base.is_number()) { .sin().into()
return Err(EvalexprError::CustomMessage("Expected two numbers".to_string())); )
}
let value: f64 = if arg_value.is_float() { arg_value.as_float()? } else { arg_value.as_int()? as f64 };
let base: f64 = if arg_base.is_float() { arg_base.as_float()? } else { arg_base.as_int()? as f64 };
output = value.log(base).into();
},
_ => {
return Err(EvalexprError::WrongFunctionArgumentAmount { expected: 2..=2, actual: count });
}
}
return Ok(output);
} }
pub fn square_root(arg: &Value) -> EvalResult { pub fn square_root(arg: &Value) -> EvalResult {
if !arg.is_number() { Ok(
return Err(EvalexprError::CustomMessage("Expected a number.".to_string())); if let Value::Float(float) = arg { float.clone() }
else if let Value::Int(int) = arg { int.clone() as f64 }
else { return Err(EvalexprError::expected_number(arg.clone())) }
.sqrt().into()
)
} }
let value: f64 = if arg.is_float() { arg.as_float()? } else { arg.as_int()? as f64 };
return Ok(value.sqrt().into()); pub fn tangent(arg: &Value) -> EvalResult {
Ok(
if let Value::Float(float) = arg { float.clone() }
else if let Value::Int(int) = arg { int.clone() as f64 }
else { return Err(EvalexprError::expected_number(arg.clone())) }
.tan().into()
)
} }

View file

@ -2,3 +2,67 @@
pub mod global; pub mod global;
pub mod helper; pub mod helper;
use pico_args::Arguments;
use evalexpr::{
context_map,
ContextWithMutableVariables,
HashMapContext,
Value
};
use crate::flag;
pub fn build(args: &mut Arguments) -> HashMapContext {
let mut output =
if !args.contains(flag::EMPTY_CONTEXT) {
context_map! {
// globals
"c" => global::LIGHT_SPEED,
"e" => global::EULER,
"phi" => global::GOLDEN_RATIO,
"pi" => global::PI,
"√2" => global::ROOT_TWO,
// math functions
"cos" => Function::new(|arg| helper::cosine(arg)),
"fix" => Function::new(|arg| helper::fix(arg)),
"log" => Function::new(|arg| helper::logarithm(arg)),
"sin" => Function::new(|arg| helper::sine(arg)),
"sqrt" => Function::new(|arg| helper::square_root(arg)),
"tan" => Function::new(|arg| helper::tangent(arg)),
// data science functions
"avg" => Function::new(|arg| helper::average(arg)),
// radix functions
"bin" => Function::new(|arg| helper::binary(arg)),
"hex" => Function::new(|arg| helper::hexadecimal(arg)),
"oct" => Function::new(|arg| helper::octal(arg)),
// character aliases
"ϕ" => global::GOLDEN_RATIO,
"π" => global::PI,
"" => Function::new(|arg| helper::square_root(arg))
}.unwrap()
} else { HashMapContext::new() };
while let Ok(value) = args.value_from_str::<&str, String>(flag::SET) {
let split: Vec<&str> = value.split('=').collect();
if split.len() == 2 {
let key = split[0].to_owned();
let value_str = split[1];
let value =
if let Ok(integer) = value_str.parse::<i64>() { Value::Int(integer) }
else if let Ok(float) = value_str.parse::<f64>() { Value::Float(float) }
else { Value::from(value_str) };
output.set_value(key, value).ok(); }
else { std::process::exit(1); }
}
output
}

View file

@ -42,7 +42,7 @@ fn main() {
return; return;
} }
let mut context = util::build_context(&mut args); let mut context = context::build(&mut args);
let quiet = args.contains(flag::QUIET) || !stdout().is_terminal(); let quiet = args.contains(flag::QUIET) || !stdout().is_terminal();
// collect args and evaluate if present // collect args and evaluate if present

View file

@ -1,13 +1,5 @@
use evalexpr::{ use evalexpr::{ EvalexprError, Value };
context_map, ContextWithMutableVariables, EvalexprError, HashMapContext, Value
};
use pico_args::Arguments;
use crate::{
context::{ global, helper },
flag
};
pub(crate) fn parse_radix(prefix: &str, base: u32, arg: &Value) -> Result<Value, EvalexprError> { pub(crate) fn parse_radix(prefix: &str, base: u32, arg: &Value) -> Result<Value, EvalexprError> {
let i_parse = arg.as_string()?; let i_parse = arg.as_string()?;
@ -22,52 +14,3 @@ pub(crate) fn parse_radix(prefix: &str, base: u32, arg: &Value) -> Result<Value,
return Ok(result.unwrap().into()); return Ok(result.unwrap().into());
} }
pub(crate) fn build_context(args: &mut Arguments) -> HashMapContext {
let mut output =
if !args.contains(flag::EMPTY_CONTEXT) {
context_map! {
// globals
"c" => global::LIGHT_SPEED,
"e" => global::EULER,
"phi" => global::GOLDEN_RATIO,
"pi" => global::PI,
"√2" => global::ROOT_TWO,
// math functions
"fix" => Function::new(|arg| helper::fix(arg)),
"log" => Function::new(|arg| helper::logarithm(arg)),
"sqrt" => Function::new(|arg| helper::square_root(arg)),
// data science functions
"avg" => Function::new(|arg| helper::average(arg)),
// radix functions
"bin" => Function::new(|arg| helper::binary(arg)),
"hex" => Function::new(|arg| helper::hexadecimal(arg)),
"oct" => Function::new(|arg| helper::octal(arg)),
// character aliases
"ϕ" => global::GOLDEN_RATIO,
"π" => global::PI,
"" => Function::new(|arg| helper::square_root(arg))
}.unwrap()
} else { HashMapContext::new() };
while let Ok(value) = args.value_from_str::<&str, String>(flag::SET) {
let split: Vec<&str> = value.split('=').collect();
if split.len() == 2 {
let key = split[0].to_owned();
let value_str = split[1];
let value =
if let Ok(integer) = value_str.parse::<i64>() { Value::Int(integer) }
else if let Ok(float) = value_str.parse::<f64>() { Value::Float(float) }
else { Value::from(value_str) };
output.set_value(key, value).ok(); }
else { std::process::exit(1); }
}
output
}