diff --git a/kalk/src/interpreter.rs b/kalk/src/interpreter.rs index 25f4ddd..0d1c4d8 100644 --- a/kalk/src/interpreter.rs +++ b/kalk/src/interpreter.rs @@ -177,10 +177,7 @@ fn eval_unary_expr( match op { TokenKind::Minus => Ok(num.mul(context, KalkNum::from(-1f64))), TokenKind::Percent => Ok(num.mul(context, KalkNum::from(0.01f64))), - TokenKind::Exclamation => Ok(KalkNum::new( - prelude::special_funcs::factorial(num.value), - unit, - )), + TokenKind::Exclamation => Ok(prelude::special_funcs::factorial(num)), _ => Err(CalcError::InvalidOperator), } } @@ -276,11 +273,13 @@ pub(crate) fn eval_fn_call_expr( 1 => { let x = eval_expr(context, &expressions[0], "")?; + // Turn eg. sqrt(-1) into i if x.value < 0f64 && (identifier.full_name == "sqrt" || identifier.full_name == "√") { + let abs_value = x.mul(context, KalkNum::from(-1f64)); let (sqrt, unit) = prelude::call_unary_func( context, &identifier.full_name, - x.value * (-1f64), + abs_value, &context.angle_unit.clone(), ) .unwrap(); @@ -288,7 +287,7 @@ pub(crate) fn eval_fn_call_expr( return Ok(KalkNum::new_with_imaginary( KalkNum::default().value, &unit, - sqrt, + sqrt.value, )); } @@ -298,14 +297,14 @@ pub(crate) fn eval_fn_call_expr( prelude::call_unary_func( context, &identifier.full_name, - x.value, + x, &context.angle_unit.clone(), ) } } 2 => { - let x = eval_expr(context, &expressions[0], "")?.value; - let y = eval_expr(context, &expressions[1], "")?.value; + let x = eval_expr(context, &expressions[0], "")?; + let y = eval_expr(context, &expressions[1], "")?; prelude::call_binary_func( context, &identifier.full_name, @@ -317,11 +316,8 @@ pub(crate) fn eval_fn_call_expr( _ => None, }; - if let Some((result, func_unit)) = prelude_func { - return Ok(KalkNum::new( - result, - if unit.len() > 0 { unit } else { &func_unit }, - )); + if let Some((result, _)) = prelude_func { + return Ok(result); } // Special functions @@ -455,7 +451,6 @@ mod tests { } fn cmp(x: KalkNum, y: f64) -> bool { - println!("{} = {}", x.to_f64(), y); (x.to_f64() - y).abs() < 0.0001 } diff --git a/kalk/src/inverter.rs b/kalk/src/inverter.rs index e9f7db5..4dc2de1 100644 --- a/kalk/src/inverter.rs +++ b/kalk/src/inverter.rs @@ -11,8 +11,8 @@ lazy_static! { pub static ref INVERSE_UNARY_FUNCS: HashMap<&'static str, &'static str> = { let mut m = HashMap::new(); m.insert("cos", "acos"); - m.insert("cosec", "acosec"); - m.insert("cosech", "cosech"); + m.insert("csc", "acsc"); + m.insert("csch", "csch"); m.insert("cosh", "acosh"); m.insert("cot", "acot"); m.insert("coth", "acoth"); @@ -24,8 +24,8 @@ lazy_static! { m.insert("tanh", "atanh"); m.insert("acos", "cos"); - m.insert("acosec", "cosec"); - m.insert("acosech", "cosech"); + m.insert("acsc", "csc"); + m.insert("acsch", "csch"); m.insert("acosh", "cosh"); m.insert("acot", "cot"); m.insert("acoth", "coth"); diff --git a/kalk/src/kalk_num/mod.rs b/kalk/src/kalk_num/mod.rs index 4d4e215..8283c6e 100644 --- a/kalk/src/kalk_num/mod.rs +++ b/kalk/src/kalk_num/mod.rs @@ -92,6 +92,10 @@ impl ScientificNotation { } impl KalkNum { + pub fn has_imaginary(&self) -> bool { + self.imaginary_value != 0f64 + } + pub fn to_scientific_notation( &self, complex_number_type: ComplexNumberType, @@ -128,7 +132,7 @@ impl KalkNum { pub fn to_string(&self) -> String { let as_str = trim_number_string(&self.to_f64().to_string()); - if self.imaginary_value != 0f64 { + if self.has_imaginary() { let imaginary_as_str = trim_number_string(&self.imaginary_to_f64().to_string()); let sign = if self.imaginary_value < 0f64 { "-" @@ -143,7 +147,7 @@ impl KalkNum { } pub fn to_string_big(&self) -> String { - if self.imaginary_value == 0f64 { + if !self.has_imaginary() { self.value.to_string() } else { let sign = if self.imaginary_value < 0f64 { @@ -196,7 +200,7 @@ impl KalkNum { }; let mut output = result_str; - if self.imaginary_value != 0f64 { + if self.has_imaginary() { // If the real value is 0, and there is an imaginary one, // clear the output so that the real value is not shown. if output == "0" { @@ -264,51 +268,22 @@ impl KalkNum { pub(crate) fn add(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - KalkNum::new_with_imaginary( - self.value + right.value, - &right.unit, - self.imaginary_value + right.imaginary_value, - ) + self.add_without_unit(right) } pub(crate) fn sub(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - KalkNum::new_with_imaginary( - self.value - right.value, - &right.unit, - self.imaginary_value - right.imaginary_value, - ) + self.sub_without_unit(right) } pub(crate) fn mul(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - // (a + bi)(c + di) = ac + adi + bci + bdi² - KalkNum::new_with_imaginary( - self.value.clone() * right.value.clone() - - self.imaginary_value.clone() * right.imaginary_value.clone(), - &right.unit, - self.value * right.imaginary_value + self.imaginary_value * right.value, - ) + self.mul_without_unit(right) } pub(crate) fn div(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs.clone()); - - // Avoid unecessary calculations - if self.imaginary_value == 0f64 && right.imaginary_value == 0f64 { - KalkNum::new(self.value / right.value, &right.unit) - } else { - // Multiply both the numerator and denominator - // with the conjugate of the denominator, and divide. - let conjugate = rhs.get_conjugate(); - let numerator = self.clone().mul(context, conjugate.clone()); - let denominator = rhs.clone().mul(context, conjugate); - KalkNum::new_with_imaginary( - numerator.value / denominator.value.clone(), - &right.unit, - numerator.imaginary_value / denominator.value, - ) - } + self.div_without_unit(right) } pub(crate) fn rem(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { @@ -316,6 +291,50 @@ impl KalkNum { KalkNum::new(self.value % right.value, &right.unit) } + pub(crate) fn add_without_unit(self, rhs: KalkNum) -> KalkNum { + KalkNum::new_with_imaginary( + self.value + rhs.value, + &rhs.unit, + self.imaginary_value + rhs.imaginary_value, + ) + } + + pub(crate) fn sub_without_unit(self, rhs: KalkNum) -> KalkNum { + KalkNum::new_with_imaginary( + self.value - rhs.value, + &rhs.unit, + self.imaginary_value - rhs.imaginary_value, + ) + } + + pub(crate) fn mul_without_unit(self, rhs: KalkNum) -> KalkNum { + // (a + bi)(c + di) = ac + adi + bci + bdi² + KalkNum::new_with_imaginary( + self.value.clone() * rhs.value.clone() + - self.imaginary_value.clone() * rhs.imaginary_value.clone(), + &rhs.unit, + self.value * rhs.imaginary_value + self.imaginary_value * rhs.value, + ) + } + + pub(crate) fn div_without_unit(self, rhs: KalkNum) -> KalkNum { + // Avoid unecessary calculations + if self.imaginary_value == 0f64 && rhs.imaginary_value == 0f64 { + KalkNum::new(self.value / rhs.value, &rhs.unit) + } else { + // Multiply both the numerator and denominator + // with the conjugate of the denominator, and divide. + let conjugate = rhs.get_conjugate(); + let numerator = self.clone().mul_without_unit(conjugate.clone()); + let denominator = rhs.clone().mul_without_unit(conjugate); + KalkNum::new_with_imaginary( + numerator.value / denominator.value.clone(), + &rhs.unit, + numerator.imaginary_value / denominator.value, + ) + } + } + pub fn get_conjugate(&self) -> KalkNum { KalkNum::new_with_imaginary( self.value.clone(), diff --git a/kalk/src/kalk_num/regular.rs b/kalk/src/kalk_num/regular.rs index 3cb803a..e19b214 100644 --- a/kalk/src/kalk_num/regular.rs +++ b/kalk/src/kalk_num/regular.rs @@ -27,6 +27,14 @@ impl KalkNum { } } + pub fn from_imaginary(value: f64) -> Self { + Self { + value: 0f64, + unit: String::new(), + imaginary_value: value, + } + } + #[wasm_bindgen(js_name = getValue)] pub fn to_f64(&self) -> f64 { self.value @@ -86,7 +94,28 @@ impl KalkNum { pub(crate) fn pow(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - KalkNum::new(self.value.powf(right.value), &right.unit) + self.pow_without_unit(right) + } + + pub(crate) fn pow_without_unit(self, rhs: KalkNum) -> KalkNum { + if self.has_imaginary() || rhs.has_imaginary() { + let a = self.value.clone(); + let b = self.imaginary_value.clone(); + let c = rhs.value; + let d = rhs.imaginary_value; + let arg = crate::prelude::funcs::arg(self).value; + let raised = a.clone() * a + b.clone() * b; + let exp = raised.clone().powf(c.clone() / 2f64) * (-d.clone() * arg.clone()).exp(); + let polar = c * arg + d / 2f64 * raised.ln(); + + KalkNum::new_with_imaginary( + polar.clone().cos() * exp.clone(), + &rhs.unit, + polar.sin() * exp, + ) + } else { + KalkNum::new(self.value.powf(rhs.value), &rhs.unit) + } } } diff --git a/kalk/src/kalk_num/with_rug.rs b/kalk/src/kalk_num/with_rug.rs index 7505b36..b7686ee 100644 --- a/kalk/src/kalk_num/with_rug.rs +++ b/kalk/src/kalk_num/with_rug.rs @@ -30,6 +30,14 @@ impl KalkNum { } } + pub fn from_imaginary(value: Float) -> Self { + Self { + value: Float::with_val(63, 0), + unit: String::new(), + imaginary_value: value, + } + } + pub fn to_f64(&self) -> f64 { self.value.to_f64_round(rug::float::Round::Nearest) } @@ -49,7 +57,28 @@ impl KalkNum { pub(crate) fn pow(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - KalkNum::new(self.value.pow(right.value), &right.unit) + self.pow_without_unit(right) + } + + pub(crate) fn pow_without_unit(self, rhs: KalkNum) -> KalkNum { + if self.has_imaginary() || rhs.has_imaginary() { + let a = self.value.clone(); + let b = self.imaginary_value.clone(); + let c = rhs.value; + let d = rhs.imaginary_value; + let arg = crate::prelude::funcs::arg(self).value; + let raised = a.clone() * a + b.clone() * b; + let exp = raised.clone().pow(c.clone() / 2f64) * (-d.clone() * arg.clone()).exp(); + let polar = c * arg + d / 2f64 * raised.ln(); + + KalkNum::new_with_imaginary( + polar.clone().cos() * exp.clone(), + &rhs.unit, + polar.sin() * exp, + ) + } else { + KalkNum::new(self.value.pow(rhs.value), &rhs.unit) + } } } diff --git a/kalk/src/prelude/mod.rs b/kalk/src/prelude/mod.rs index 4aa7e94..1ed89eb 100644 --- a/kalk/src/prelude/mod.rs +++ b/kalk/src/prelude/mod.rs @@ -1,7 +1,26 @@ +use crate::kalk_num::KalkNum; use lazy_static::lazy_static; use std::collections::HashMap; use FuncType::*; +#[cfg(feature = "rug")] +pub mod with_rug; +#[cfg(feature = "rug")] +pub use with_rug::funcs::*; +#[cfg(feature = "rug")] +pub use with_rug::*; + +#[cfg(not(feature = "rug"))] +pub mod regular; +#[cfg(not(feature = "rug"))] +pub use regular::funcs::*; +#[cfg(not(feature = "rug"))] +pub use regular::*; + +use crate::ast::Expr; +use crate::interpreter; +pub use funcs::*; + // `i` is added in the symbol_table module, since for some reason it didn't work here. pub const INIT: &'static str = "unit deg = (rad*180)/pi"; @@ -41,8 +60,8 @@ lazy_static! { pub static ref UNARY_FUNCS: HashMap<&'static str, (UnaryFuncInfo, &'static str)> = { let mut m = HashMap::new(); m.insert("cos", (UnaryFuncInfo(cos, Trig), "")); - m.insert("cosec", (UnaryFuncInfo(cosec, Trig), "")); - m.insert("cosech", (UnaryFuncInfo(cosech, Trig), "")); + m.insert("csc", (UnaryFuncInfo(csc, Trig), "")); + m.insert("csch", (UnaryFuncInfo(csch, Trig), "")); m.insert("cosh", (UnaryFuncInfo(cosh, Trig), "")); m.insert("cot", (UnaryFuncInfo(cot, Trig), "")); m.insert("coth", (UnaryFuncInfo(coth, Trig), "")); @@ -54,8 +73,8 @@ lazy_static! { m.insert("tanh", (UnaryFuncInfo(tanh, Trig), "")); m.insert("acos", (UnaryFuncInfo(acos, InverseTrig), "rad")); - m.insert("acosec", (UnaryFuncInfo(acosec, InverseTrig), "rad")); - m.insert("acosech", (UnaryFuncInfo(acosech, InverseTrig), "rad")); + m.insert("acsc", (UnaryFuncInfo(acsc, InverseTrig), "rad")); + m.insert("acsch", (UnaryFuncInfo(acsch, InverseTrig), "rad")); m.insert("acosh", (UnaryFuncInfo(acosh, InverseTrig), "rad")); m.insert("acot", (UnaryFuncInfo(acot, InverseTrig), "rad")); m.insert("acoth", (UnaryFuncInfo(acoth, InverseTrig), "rad")); @@ -66,6 +85,7 @@ lazy_static! { m.insert("atan", (UnaryFuncInfo(atan, InverseTrig), "rad")); m.insert("atanh", (UnaryFuncInfo(atanh, InverseTrig), "rad")); + m.insert("arg", (UnaryFuncInfo(arg, Other), "")); m.insert("abs", (UnaryFuncInfo(abs, Other), "")); m.insert("cbrt", (UnaryFuncInfo(cbrt, Other), "")); m.insert("ceil", (UnaryFuncInfo(ceil, Other), "")); @@ -86,7 +106,7 @@ lazy_static! { let mut m = HashMap::new(); m.insert("max", (BinaryFuncInfo(max, Other), "")); m.insert("min", (BinaryFuncInfo(min, Other), "")); - m.insert("hyp", (BinaryFuncInfo(hyp, Other), "")); + m.insert("hypot", (BinaryFuncInfo(hypot, Other), "")); m.insert("log", (BinaryFuncInfo(logx, Other), "")); m.insert("root", (BinaryFuncInfo(nth_root, Other), "")); m @@ -99,16 +119,496 @@ enum FuncType { Other, } -#[cfg(feature = "rug")] -pub mod with_rug; -#[cfg(feature = "rug")] -pub use with_rug::funcs::*; -#[cfg(feature = "rug")] -pub use with_rug::*; +// Unary functions +pub struct UnaryFuncInfo(fn(KalkNum) -> KalkNum, FuncType); -#[cfg(not(feature = "rug"))] -pub mod regular; -#[cfg(not(feature = "rug"))] -pub use regular::funcs::*; -#[cfg(not(feature = "rug"))] -pub use regular::*; +pub struct BinaryFuncInfo(fn(KalkNum, KalkNum) -> KalkNum, FuncType); + +impl UnaryFuncInfo { + fn call(&self, context: &mut interpreter::Context, x: KalkNum, angle_unit: &str) -> KalkNum { + let func = self.0; + match self.1 { + FuncType::Trig => func(from_angle_unit(context, x, angle_unit)), + FuncType::InverseTrig => to_angle_unit(context, func(x), angle_unit), + FuncType::Other => func(x), + } + } +} + +impl BinaryFuncInfo { + fn call( + &self, + context: &mut interpreter::Context, + x: KalkNum, + y: KalkNum, + angle_unit: &str, + ) -> KalkNum { + let func = self.0; + match self.1 { + FuncType::Trig => func( + from_angle_unit(context, x, angle_unit), + from_angle_unit(context, y, angle_unit), + ), + FuncType::InverseTrig => to_angle_unit(context, func(x, y), angle_unit), + FuncType::Other => func(x, y), + } + } +} + +pub fn is_prelude_func(identifier: &str) -> bool { + identifier == "sum" + || identifier == "Σ" + || identifier == "integrate" + || identifier == "∫" + || UNARY_FUNCS.contains_key(identifier) + || BINARY_FUNCS.contains_key(identifier) +} + +pub fn call_unary_func( + context: &mut interpreter::Context, + name: &str, + x: KalkNum, + angle_unit: &str, +) -> Option<(KalkNum, String)> { + if let Some((func_info, func_unit)) = UNARY_FUNCS.get(name) { + Some(( + func_info.call(context, x, &angle_unit), + func_unit.to_string(), + )) + } else { + None + } +} + +pub fn call_binary_func( + context: &mut interpreter::Context, + name: &str, + x: KalkNum, + y: KalkNum, + angle_unit: &str, +) -> Option<(KalkNum, String)> { + if let Some((func_info, func_unit)) = BINARY_FUNCS.get(name) { + Some(( + func_info.call(context, x, y, angle_unit), + func_unit.to_string(), + )) + } else { + None + } +} + +fn to_angle_unit(context: &mut interpreter::Context, x: KalkNum, angle_unit: &str) -> KalkNum { + match angle_unit { + "rad" => x, + _ => interpreter::convert_unit(context, &Expr::Literal(x.to_f64()), "rad", angle_unit) + .unwrap(), + } +} + +fn from_angle_unit(context: &mut interpreter::Context, x: KalkNum, angle_unit: &str) -> KalkNum { + match angle_unit { + "rad" => x, + _ => interpreter::convert_unit(context, &Expr::Literal(x.to_f64()), angle_unit, "rad") + .unwrap(), + } +} + +pub mod funcs { + #[cfg(not(feature = "rug"))] + pub use super::regular::funcs::*; + #[cfg(feature = "rug")] + pub use super::with_rug::funcs::*; + use crate::kalk_num::KalkNum; + + pub fn abs(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + // |z| = sqrt(a² + b²) + let a = x.value.clone() * x.value; + let b = x.imaginary_value.clone() * x.imaginary_value; + + sqrt(KalkNum::new(a + b, &x.unit)) + } else { + KalkNum::new(x.value.abs(), &x.unit) + } + } + + pub fn acos(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + // -i * ln(i * sqrt(1 - z²) + z) + let root = + sqrt(KalkNum::from(1f64).sub_without_unit(x.clone().mul_without_unit(x.clone()))); + let iroot = multiply_with_i(root.clone()); + let ln = ln(iroot.add_without_unit(x)); + + // -iz = -i(a + bi) = b - ai + KalkNum::new_with_imaginary(ln.imaginary_value, &ln.unit, -ln.value) + } else { + KalkNum::new(x.value.acos(), &x.unit) + } + } + + pub fn acosh(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + let sqrt1 = sqrt(KalkNum::new_with_imaginary( + x.value.clone() + 1f64, + &x.unit, + x.imaginary_value.clone(), + )); + let sqrt2 = sqrt(KalkNum::new_with_imaginary( + x.value.clone() - 1f64, + &x.unit, + x.imaginary_value.clone(), + )); + + ln(x.add_without_unit(sqrt1.mul_without_unit(sqrt2))) + } else { + KalkNum::new(x.value.acosh(), &x.unit) + } + } + + pub fn acot(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + // atan(1/z) + atan(KalkNum::from(1f64).div_without_unit(x)) + } else { + KalkNum::new((1f64 / x.value).atan(), &x.unit) + } + } + + pub fn acoth(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + // 1 / z + let inv_x = KalkNum::from(1f64).div_without_unit(x); + let ln1 = ln(KalkNum::new_with_imaginary( + 1f64 + inv_x.value.clone(), + &inv_x.unit, + inv_x.imaginary_value.clone(), + )); + let ln2 = ln(KalkNum::new_with_imaginary( + 1f64 - inv_x.value, + &inv_x.unit, + -inv_x.imaginary_value, + )); + + ln1.sub_without_unit(ln2) + .div_without_unit(KalkNum::from(2f64)) + } else { + KalkNum::new((1f64 / x.value).atanh(), &x.unit) + } + } + + pub fn acsc(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + // asin(1/z) + asin(KalkNum::from(1f64).div_without_unit(x)) + } else { + KalkNum::new((1f64 / x.value).asin(), &x.unit) + } + } + + pub fn acsch(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + let inv_x2 = + KalkNum::from(1f64).div_without_unit(x.clone().mul_without_unit(x.clone())); + let sqrt = sqrt(KalkNum::new_with_imaginary( + 1f64 + inv_x2.value, + &inv_x2.unit, + inv_x2.imaginary_value, + )); + let inv_x = KalkNum::from(1f64).div_without_unit(x.clone()); + + ln(sqrt.add_without_unit(inv_x)) + } else { + KalkNum::new((1f64 / x.value).asinh(), &x.unit) + } + } + + pub fn asec(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + // acos(1/z) + acos(KalkNum::from(1f64).div_without_unit(x)) + } else { + KalkNum::new((1f64 / x.value).acos(), &x.unit) + } + } + + pub fn asech(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + // 1/z + let inv_x = KalkNum::from(1f64).div_without_unit(x.clone()); + // sqrt(1/z - 1) + let sqrt1 = sqrt(KalkNum::new_with_imaginary( + inv_x.value.clone() - 1f64, + &inv_x.unit, + inv_x.imaginary_value.clone(), + )); + // sqrt(1/z + 1) + let sqrt2 = sqrt(KalkNum::new_with_imaginary( + inv_x.value.clone() + 1f64, + &inv_x.unit, + inv_x.imaginary_value.clone(), + )); + + // ln(1/z + sqrt(1/z - 1) * sqrt(1/z + 1)) + ln(sqrt1.mul_without_unit(sqrt2).add_without_unit(inv_x)) + } else { + KalkNum::new((1f64 / x.value).acosh(), &x.unit) + } + } + + pub fn asin(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + // i * ln(sqrt(1 - z²) - iz) + let root = + sqrt(KalkNum::from(1f64).sub_without_unit(x.clone().mul_without_unit(x.clone()))); + let iz = multiply_with_i(x.clone()); + let ln = ln(root.sub_without_unit(iz)); + multiply_with_i(ln) + } else { + KalkNum::new(x.value.asin(), &x.unit) + } + } + + pub fn asinh(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + let x2 = x.clone().mul_without_unit(x.clone()); + let sqrt = sqrt(KalkNum::new_with_imaginary( + x2.value + 1f64, + &x2.unit, + x2.imaginary_value, + )); + + ln(x.add_without_unit(sqrt)) + } else { + KalkNum::new(x.value.asinh(), &x.unit) + } + } + + pub fn atan(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + let iz = multiply_with_i(x); + // 1 + iz + let numerator = KalkNum::new_with_imaginary( + 1f64 + iz.value.clone(), + &iz.unit, + iz.imaginary_value.clone(), + ); + // 1 - iz + let denominator = + KalkNum::new_with_imaginary(1f64 - iz.value, &iz.unit, -iz.imaginary_value); + let ln = ln(numerator.div_without_unit(denominator)); + + // -0.5iz = -0.5i(a + bi) = b/2 - ai/2 + multiply_with_i(ln).div_without_unit(KalkNum::from(-2f64)) + } else { + KalkNum::new(x.value.atan(), &x.unit) + } + } + + pub fn atanh(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + // 1/2 * log(z + 1) - 1/2 * log(-z + 1) + let log1 = ln(KalkNum::new_with_imaginary( + 1f64 + x.value.clone(), + &x.unit, + x.imaginary_value.clone(), + )); + let log2 = ln(KalkNum::new_with_imaginary( + 1f64 - x.value, + &x.unit, + -x.imaginary_value, + )); + + log1.sub_without_unit(log2) + .div_without_unit(KalkNum::from(2f64)) + } else { + KalkNum::new(x.value.atanh(), &x.unit) + } + } + + pub fn cbrt(x: KalkNum) -> KalkNum { + KalkNum::new(x.value.cbrt(), &x.unit) + } + + pub fn ceil(x: KalkNum) -> KalkNum { + KalkNum::new_with_imaginary(x.value.ceil(), &x.unit, x.imaginary_value.ceil()) + } + + pub fn cos(x: KalkNum) -> KalkNum { + KalkNum::new_with_imaginary( + x.value.clone().cos() * x.imaginary_value.clone().cosh(), + &x.unit, + -x.value.sin() * x.imaginary_value.sinh(), + ) + } + + pub fn cosh(x: KalkNum) -> KalkNum { + KalkNum::new_with_imaginary( + x.value.clone().cosh() * x.imaginary_value.clone().cos(), + &x.unit, + x.value.sinh() * x.imaginary_value.sin(), + ) + } + + pub fn csc(x: KalkNum) -> KalkNum { + KalkNum::from(1f64).div_without_unit(sin(x)) + } + + pub fn csch(x: KalkNum) -> KalkNum { + KalkNum::from(1f64).div_without_unit(sinh(x)) + } + + pub fn cot(x: KalkNum) -> KalkNum { + let a = x.value * 2f64; + let b = x.imaginary_value * 2f64; + KalkNum::new_with_imaginary( + -a.clone().sin() / (a.clone().cos() - b.clone().cosh()), + &x.unit, + b.clone().sinh() / (a.cos() - b.cosh()), + ) + } + + pub fn coth(x: KalkNum) -> KalkNum { + let a = x.value * 2f64; + let b = x.imaginary_value * 2f64; + KalkNum::new_with_imaginary( + -a.clone().sinh() / (b.clone().cos() - a.clone().cosh()), + &x.unit, + b.clone().sin() / (b.cos() - a.cosh()), + ) + } + + pub fn exp(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + // e^a*cos(b) + ie^a*sin(b) + let exp_a = x.value.exp(); + let b = x.imaginary_value; + KalkNum::new_with_imaginary(exp_a.clone() * b.clone().cos(), &x.unit, exp_a * b.sin()) + } else { + KalkNum::new(x.value.exp(), &x.unit) + } + } + + pub fn floor(x: KalkNum) -> KalkNum { + KalkNum::new_with_imaginary(x.value.floor(), &x.unit, x.imaginary_value.floor()) + } + + pub fn frac(x: KalkNum) -> KalkNum { + KalkNum::new_with_imaginary(x.value.fract(), &x.unit, x.imaginary_value.fract()) + } + + pub fn log(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + // ln(z) / ln(10) + ln(x).div_without_unit(KalkNum::from(10f64.ln())) + } else { + KalkNum::new(x.value.log10(), &x.unit) + } + } + + pub fn logx(x: KalkNum, y: KalkNum) -> KalkNum { + if x.has_imaginary() || y.has_imaginary() { + // ln(z) / ln(n) + ln(x).div_without_unit(ln(y)) + } else { + KalkNum::new(x.value.log10() / y.value.log10(), &x.unit) + } + } + + pub fn ln(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + let r = abs(x.clone()); + // ln|z| + i * arg z + ln(r).add_without_unit(multiply_with_i(arg(x))) + } else { + KalkNum::new(x.value.ln(), &x.unit) + } + } + + pub fn nth_root(x: KalkNum, n: KalkNum) -> KalkNum { + x.pow_without_unit(KalkNum::from(1f64).div_without_unit(n)) + } + + pub fn round(x: KalkNum) -> KalkNum { + KalkNum::new_with_imaginary(x.value.round(), &x.unit, x.imaginary_value.round()) + } + + pub fn sec(x: KalkNum) -> KalkNum { + KalkNum::from(1f64).div_without_unit(cos(x)) + } + + pub fn sech(x: KalkNum) -> KalkNum { + KalkNum::from(1f64).div_without_unit(cosh(x)) + } + + pub fn sin(x: KalkNum) -> KalkNum { + KalkNum::new_with_imaginary( + x.value.clone().sin() * x.imaginary_value.clone().cosh(), + &x.unit, + x.value.cos() * x.imaginary_value.sinh(), + ) + } + + pub fn sinh(x: KalkNum) -> KalkNum { + KalkNum::new_with_imaginary( + x.value.clone().sinh() * x.imaginary_value.clone().cos(), + &x.unit, + x.value.cosh() * x.imaginary_value.sin(), + ) + } + + pub fn sqrt(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + let abs = abs(x.clone()); + let r = abs.value; + let a = x.value; + let b = x.imaginary_value; + + // sqrt((|z| + a) / 2) + i * (b / |b|) * sqrt((|z| - a) / 2) + KalkNum::new_with_imaginary( + ((r.clone() + a.clone()) / 2f64).sqrt(), + &abs.unit, + (b.clone() / b.abs()) * ((r - a) / 2f64).sqrt(), + ) + } else { + KalkNum::new(x.value.sqrt(), &x.unit) + } + } + + pub fn tan(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + let a = x.value * 2f64; + let b = x.imaginary_value * 2f64; + KalkNum::new_with_imaginary( + a.clone().sin() / (a.clone().cos() + b.clone().cosh()), + &x.unit, + b.clone().sinh() / (a.cos() + b.cosh()), + ) + } else { + KalkNum::new(x.value.tan(), &x.unit) + } + } + + pub fn tanh(x: KalkNum) -> KalkNum { + if x.has_imaginary() { + let a = x.value * 2f64; + let b = x.imaginary_value * 2f64; + KalkNum::new_with_imaginary( + a.clone().sinh() / (a.clone().cosh() + b.clone().cos()), + &x.unit, + b.clone().sin() / (a.cosh() + b.cos()), + ) + } else { + KalkNum::new(x.value.tanh(), &x.unit) + } + } + + pub fn trunc(x: KalkNum) -> KalkNum { + KalkNum::new_with_imaginary(x.value.trunc(), &x.unit, x.imaginary_value.trunc()) + } + + fn multiply_with_i(z: KalkNum) -> KalkNum { + // iz = i(a + bi) = -b + ai + KalkNum::new_with_imaginary(-z.imaginary_value, &z.unit, z.value) + } +} diff --git a/kalk/src/prelude/regular.rs b/kalk/src/prelude/regular.rs index ecead60..41bbe5a 100644 --- a/kalk/src/prelude/regular.rs +++ b/kalk/src/prelude/regular.rs @@ -1,211 +1,34 @@ -use super::*; -use crate::ast::Expr; -use crate::interpreter; - -// Unary functions -pub struct UnaryFuncInfo(pub(super) fn(f64) -> f64, pub(super) FuncType); - -pub struct BinaryFuncInfo(pub(super) fn(f64, f64) -> f64, pub(super) FuncType); - -impl UnaryFuncInfo { - fn call(&self, context: &mut interpreter::Context, x: f64, angle_unit: &str) -> f64 { - let func = self.0; - match self.1 { - FuncType::Trig => func(from_angle_unit(context, x, angle_unit)), - FuncType::InverseTrig => to_angle_unit(context, func(x), angle_unit), - FuncType::Other => func(x), - } - } -} - -impl BinaryFuncInfo { - fn call(&self, context: &mut interpreter::Context, x: f64, y: f64, angle_unit: &str) -> f64 { - let func = self.0; - match self.1 { - FuncType::Trig => func( - from_angle_unit(context, x, angle_unit), - from_angle_unit(context, y, angle_unit), - ), - FuncType::InverseTrig => to_angle_unit(context, func(x, y), angle_unit), - FuncType::Other => func(x, y), - } - } -} - -pub fn is_prelude_func(identifier: &str) -> bool { - identifier == "sum" - || identifier == "Σ" - || identifier == "integrate" - || identifier == "∫" - || UNARY_FUNCS.contains_key(identifier) - || BINARY_FUNCS.contains_key(identifier) -} - -pub fn call_unary_func( - context: &mut interpreter::Context, - name: &str, - x: f64, - angle_unit: &str, -) -> Option<(f64, String)> { - if let Some((func_info, func_unit)) = UNARY_FUNCS.get(name) { - Some(( - func_info.call(context, x, &angle_unit), - func_unit.to_string(), - )) - } else { - None - } -} - -pub fn call_binary_func( - context: &mut interpreter::Context, - name: &str, - x: f64, - y: f64, - angle_unit: &str, -) -> Option<(f64, String)> { - if let Some((func_info, func_unit)) = BINARY_FUNCS.get(name) { - Some(( - func_info.call(context, x, y, angle_unit), - func_unit.to_string(), - )) - } else { - None - } -} - -fn to_angle_unit(context: &mut interpreter::Context, x: f64, angle_unit: &str) -> f64 { - match angle_unit { - "rad" => x, - _ => { - interpreter::convert_unit(context, &Expr::Literal(x), "rad", angle_unit) - .unwrap() - .value - } - } -} - -fn from_angle_unit(context: &mut interpreter::Context, x: f64, angle_unit: &str) -> f64 { - match angle_unit { - "rad" => x, - _ => { - interpreter::convert_unit(context, &Expr::Literal(x), angle_unit, "rad") - .unwrap() - .value - } - } -} - pub mod special_funcs { - pub fn factorial(x: f64) -> f64 { - super::funcs::gamma(x + 1f64) + use crate::kalk_num::KalkNum; + + pub fn factorial(x: KalkNum) -> KalkNum { + // Round it a bit, to prevent floating point errors. + KalkNum::new( + (super::funcs::precise_gamma(x.value + 1f64) * 10e6f64).round() / 10e6f64, + &x.unit, + ) } } -pub(super) mod funcs { - pub fn abs(x: f64) -> f64 { - x.abs() +pub(crate) mod funcs { + use crate::kalk_num::KalkNum; + use crate::prelude::funcs::abs; + + pub fn arg(x: KalkNum) -> KalkNum { + // i(ln|x| - ln(x)) + KalkNum::new(x.imaginary_value.atan2(x.value), &x.unit) } - pub fn acos(x: f64) -> f64 { - x.acos() - } - - pub fn acosh(x: f64) -> f64 { - x.acosh() - } - - pub fn acot(x: f64) -> f64 { - (1f64 / x).atan() - } - - pub fn acoth(x: f64) -> f64 { - (1f64 / x).atanh() - } - - pub fn acosec(x: f64) -> f64 { - (1f64 / x).asin() - } - - pub fn acosech(x: f64) -> f64 { - (1f64 / x).asinh() - } - - pub fn asec(x: f64) -> f64 { - (1f64 / x).acos() - } - - pub fn asech(x: f64) -> f64 { - (1f64 / x).acosh() - } - - pub fn asin(x: f64) -> f64 { - x.asin() - } - - pub fn asinh(x: f64) -> f64 { - x.asinh() - } - - pub fn atan(x: f64) -> f64 { - x.atan() - } - - pub fn atanh(x: f64) -> f64 { - x.atanh() - } - - pub fn cbrt(x: f64) -> f64 { - x.cbrt() - } - - pub fn ceil(x: f64) -> f64 { - x.ceil() - } - - pub fn cos(x: f64) -> f64 { - x.cos() - } - - pub fn cosh(x: f64) -> f64 { - x.cos() - } - - pub fn cosec(x: f64) -> f64 { - 1f64 / x.sin() - } - - pub fn cosech(x: f64) -> f64 { - 1f64 / x.sinh() - } - - pub fn cot(x: f64) -> f64 { - x.clone().cos() / x.sin() - } - - pub fn coth(x: f64) -> f64 { - x.clone().cosh() / x.sinh() - } - - pub fn exp(x: f64) -> f64 { - x.exp() - } - - pub fn floor(x: f64) -> f64 { - x.floor() - } - - pub fn frac(x: f64) -> f64 { - x.fract() - } - - pub fn gamma(x: f64) -> f64 { + pub fn gamma(x: KalkNum) -> KalkNum { // Round it a bit, to prevent floating point errors. - (precise_gamma(x) * 10e6f64).round() / 10e6f64 + KalkNum::new( + (precise_gamma(x.value) * 10e6f64).round() / 10e6f64, + &x.unit, + ) } // Matthias Eiholzer - https://gitlab.com/matthiaseiholzer/mathru/-/tree/master - fn precise_gamma(x: f64) -> f64 { + pub(super) fn precise_gamma(x: f64) -> f64 { let pi = 3.1415926535897932384626433832795028841971693993751058209749445923f64; if x == 0f64 { return f64::NAN; @@ -227,67 +50,26 @@ pub(super) mod funcs { 2f64.sqrt() * pi.sqrt() * t.powf(x - 0.5f64) * (-t).exp() * a } - pub fn hyp(x: f64, y: f64) -> f64 { - x.hypot(y) + pub fn hypot(x: KalkNum, y: KalkNum) -> KalkNum { + if x.has_imaginary() || y.has_imaginary() { + let abs_x = abs(x); + let abs_y = abs(y); + crate::prelude::funcs::sqrt( + abs_x + .clone() + .mul_without_unit(abs_x) + .add_without_unit(abs_y.clone().mul_without_unit(abs_y)), + ) + } else { + KalkNum::new(x.value.hypot(y.value), &x.unit) + } } - pub fn log(x: f64) -> f64 { - x.log10() + pub fn max(x: KalkNum, y: KalkNum) -> KalkNum { + KalkNum::new(x.value.max(y.value), &x.unit) } - pub fn logx(x: f64, y: f64) -> f64 { - x.log10() / y.log10() - } - - pub fn ln(x: f64) -> f64 { - x.ln() - } - - pub fn max(x: f64, y: f64) -> f64 { - x.max(y) - } - - pub fn min(x: f64, y: f64) -> f64 { - x.min(y) - } - - pub fn round(x: f64) -> f64 { - x.round() - } - - pub fn sec(x: f64) -> f64 { - 1f64 / x.cos() - } - - pub fn sech(x: f64) -> f64 { - 1f64 / x.cosh() - } - - pub fn sin(x: f64) -> f64 { - x.sin() - } - - pub fn sinh(x: f64) -> f64 { - x.sinh() - } - - pub fn sqrt(x: f64) -> f64 { - x.sqrt() - } - - pub fn nth_root(x: f64, n: f64) -> f64 { - x.powf(1f64 / n) - } - - pub fn tan(x: f64) -> f64 { - x.tan() - } - - pub fn tanh(x: f64) -> f64 { - x.tanh() - } - - pub fn trunc(x: f64) -> f64 { - x.trunc() + pub fn min(x: KalkNum, y: KalkNum) -> KalkNum { + KalkNum::new(x.value.min(y.value), &x.unit) } } diff --git a/kalk/src/prelude/with_rug.rs b/kalk/src/prelude/with_rug.rs index 36ee44e..1adf48a 100644 --- a/kalk/src/prelude/with_rug.rs +++ b/kalk/src/prelude/with_rug.rs @@ -1,291 +1,44 @@ -use super::*; -use crate::ast::Expr; -use crate::interpreter; -use rug::Float; - -// Unary functions -pub struct UnaryFuncInfo(pub(super) fn(Float) -> Float, pub(super) FuncType); - -pub struct BinaryFuncInfo(pub(super) fn(Float, Float) -> Float, pub(super) FuncType); - -impl UnaryFuncInfo { - fn call(&self, context: &mut interpreter::Context, x: Float, angle_unit: &str) -> Float { - let func = self.0; - match self.1 { - FuncType::Trig => func(from_angle_unit(context, x, angle_unit)), - FuncType::InverseTrig => to_angle_unit(context, func(x), angle_unit), - FuncType::Other => func(x), - } - } -} - -impl BinaryFuncInfo { - fn call( - &self, - context: &mut interpreter::Context, - x: Float, - y: Float, - angle_unit: &str, - ) -> Float { - let func = self.0; - match self.1 { - FuncType::Trig => func( - from_angle_unit(context, x, angle_unit), - from_angle_unit(context, y, angle_unit), - ), - FuncType::InverseTrig => to_angle_unit(context, func(x, y), angle_unit), - FuncType::Other => func(x, y), - } - } -} - -pub fn is_prelude_func(identifier: &str) -> bool { - identifier == "sum" - || identifier == "Σ" - || identifier == "integrate" - || identifier == "∫" - || UNARY_FUNCS.contains_key(identifier) - || BINARY_FUNCS.contains_key(identifier) -} - -pub fn call_unary_func( - context: &mut interpreter::Context, - name: &str, - x: Float, - angle_unit: &str, -) -> Option<(Float, String)> { - if let Some((func_info, func_unit)) = UNARY_FUNCS.get(name) { - Some(( - func_info.call(context, x, &angle_unit), - func_unit.to_string(), - )) - } else { - None - } -} - -pub fn call_binary_func( - context: &mut interpreter::Context, - name: &str, - x: Float, - y: Float, - angle_unit: &str, -) -> Option<(Float, String)> { - if let Some((func_info, func_unit)) = BINARY_FUNCS.get(name) { - Some(( - func_info.call(context, x, y, angle_unit), - func_unit.to_string(), - )) - } else { - None - } -} - -fn to_angle_unit(context: &mut interpreter::Context, x: Float, angle_unit: &str) -> Float { - match angle_unit { - "rad" => x, - _ => { - interpreter::convert_unit( - context, - &Expr::Literal(x.to_f64_round(rug::float::Round::Nearest)), - "rad", - angle_unit, - ) - .unwrap() - .value - } - } -} - -fn from_angle_unit(context: &mut interpreter::Context, x: Float, angle_unit: &str) -> Float { - match angle_unit { - "rad" => x, - _ => { - interpreter::convert_unit( - context, - &Expr::Literal(x.to_f64_round(rug::float::Round::Nearest)), - angle_unit, - "rad", - ) - .unwrap() - .value - } - } -} - pub mod special_funcs { - use rug::Float; + use crate::prelude::KalkNum; - pub fn factorial(x: Float) -> Float { - ((x + 1) as Float).gamma() + pub fn factorial(x: KalkNum) -> KalkNum { + KalkNum::new((x.value + 1f64).gamma(), &x.unit) } } -pub(super) mod funcs { - use rug::ops::Pow; - use rug::Float; +pub(crate) mod funcs { + use crate::kalk_num::KalkNum; + use crate::prelude::funcs::abs; - pub fn abs(x: Float) -> Float { - x.abs() + pub fn arg(x: KalkNum) -> KalkNum { + // i(ln|x| - ln(x)) + KalkNum::new(x.imaginary_value.atan2(&x.value), &x.unit) } - pub fn acos(x: Float) -> Float { - x.acos() + pub fn gamma(x: KalkNum) -> KalkNum { + KalkNum::new(x.value.gamma(), &x.unit) } - pub fn acosh(x: Float) -> Float { - x.acosh() + pub fn hypot(x: KalkNum, y: KalkNum) -> KalkNum { + if x.has_imaginary() || y.has_imaginary() { + let abs_x = abs(x); + let abs_y = abs(y); + crate::prelude::funcs::sqrt( + abs_x + .clone() + .mul_without_unit(abs_x) + .add_without_unit(abs_y.clone().mul_without_unit(abs_y)), + ) + } else { + KalkNum::new(x.value.hypot(&y.value), &x.unit) + } } - pub fn acot(x: Float) -> Float { - (1f64 / x).atan() + pub fn max(x: KalkNum, y: KalkNum) -> KalkNum { + KalkNum::new(x.value.max(&y.value), &x.unit) } - pub fn acoth(x: Float) -> Float { - (1f64 / x).atanh() - } - - pub fn acosec(x: Float) -> Float { - (1f64 / x).asin() - } - - pub fn acosech(x: Float) -> Float { - (1f64 / x).asinh() - } - - pub fn asec(x: Float) -> Float { - (1f64 / x).acos() - } - - pub fn asech(x: Float) -> Float { - (1f64 / x).acosh() - } - - pub fn asin(x: Float) -> Float { - x.asin() - } - - pub fn asinh(x: Float) -> Float { - x.asinh() - } - - pub fn atan(x: Float) -> Float { - x.atan() - } - - pub fn atanh(x: Float) -> Float { - x.atanh() - } - - pub fn cbrt(x: Float) -> Float { - x.cbrt() - } - - pub fn ceil(x: Float) -> Float { - x.ceil() - } - - pub fn cos(x: Float) -> Float { - x.cos() - } - - pub fn cosh(x: Float) -> Float { - x.cos() - } - - pub fn cosec(x: Float) -> Float { - 1f64 / x.sin() - } - - pub fn cosech(x: Float) -> Float { - 1f64 / x.sinh() - } - - pub fn cot(x: Float) -> Float { - x.clone().cos() / x.sin() - } - - pub fn coth(x: Float) -> Float { - x.clone().cosh() / x.sinh() - } - - pub fn exp(x: Float) -> Float { - x.exp() - } - - pub fn floor(x: Float) -> Float { - x.floor() - } - - pub fn frac(x: Float) -> Float { - x.fract() - } - - pub fn gamma(x: Float) -> Float { - x.gamma() - } - - pub fn hyp(x: Float, y: Float) -> Float { - x.hypot(&y) - } - - pub fn log(x: Float) -> Float { - x.log10() - } - - pub fn logx(x: Float, y: Float) -> Float { - x.log10() / y.log10() - } - - pub fn ln(x: Float) -> Float { - x.ln() - } - - pub fn max(x: Float, y: Float) -> Float { - x.max(&y) - } - - pub fn min(x: Float, y: Float) -> Float { - x.min(&y) - } - - pub fn round(x: Float) -> Float { - x.round() - } - - pub fn sec(x: Float) -> Float { - 1f64 / x.cos() - } - - pub fn sech(x: Float) -> Float { - 1f64 / x.cosh() - } - - pub fn sin(x: Float) -> Float { - x.sin() - } - - pub fn sinh(x: Float) -> Float { - x.sinh() - } - - pub fn sqrt(x: Float) -> Float { - x.sqrt() - } - - pub fn nth_root(x: Float, n: Float) -> Float { - x.pow(Float::with_val(1, 1) / n) - } - - pub fn tan(x: Float) -> Float { - x.tan() - } - - pub fn tanh(x: Float) -> Float { - x.tanh() - } - - pub fn trunc(x: Float) -> Float { - x.trunc() + pub fn min(x: KalkNum, y: KalkNum) -> KalkNum { + KalkNum::new(x.value.min(&y.value), &x.unit) } } diff --git a/kalk_cli/help.txt b/kalk_cli/help.txt index e8a5d26..28838ed 100644 --- a/kalk_cli/help.txt +++ b/kalk_cli/help.txt @@ -40,14 +40,14 @@ They are used like this: name(arg1, arg2, etc.) Example: f(3) + 3 A(2, 3) Predefined functions - sin, cos, tan, cot, cosec, sec - sinh, cosh, tanh, coth, cosech, sech - asin, acos, atan, acot, acosec, asec - asinh, acosh, atanh, acoth, acosech, asech + sin, cos, tan, cot, csc, sec + sinh, cosh, tanh, coth, csch, sech + asin, acos, atan, acot, acsc, asec + asinh, acosh, atanh, acoth, acsch, asech abs, ceil or ⌈⌉, floor or ⌊⌋, frac, round, trunc sqrt or √, cbrt, exp, log, ln gamma or Γ - asinh, acosh, atanh, acoth, acosech, asech + asinh, acosh, atanh, acoth, acsch, asech min, max, hyp log Eg. log(1000, 10) is the same as log10(1000) root Eg. root(16, 3) is the same as 3√16