#[cfg(feature = "decimal")]
use crate::Decimal;
use crate::{Context, Error, ErrorKind, Evaluator, Number};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Math;
fn finite<T: Into<f64>>(value: T) -> bool {
value.into().is_finite()
}
macro_rules! math {
(
$type:ident in $module:ident,
unary: [$($unary:ident = $unary_libm:ident),* $(,)?],
binary: [$($binary:ident = $binary_libm:ident),* $(,)?] $(,)?
) => {
#[doc = concat!("`libm` functions for `", stringify!($type), "`, under the names expressions use.")]
mod $module {
$(
#[doc = concat!("Calls `libm::", stringify!($unary_libm), "`.")]
pub(super) fn $unary(x: $type) -> $type {
libm::$unary_libm(x)
})*
$(
#[doc = concat!("Calls `libm::", stringify!($binary_libm), "`.")]
pub(super) fn $binary(x: $type, y: $type) -> $type {
libm::$binary_libm(x, y)
})*
}
impl Context<$type> for Math {
fn variable(&self, name: &str) -> Option<$type> {
use core::$type::consts;
match name {
"pi" | "PI" => Some(consts::PI),
"tau" => Some(consts::TAU),
"e" | "E" => Some(consts::E),
_ => None,
}
}
fn call(&self, name: &str, arguments: &[$type]) -> Option<Result<$type, ErrorKind>> {
use $module::*;
let domain = |valid: bool, value: $type| if valid { Ok(value) } else { Err(ErrorKind::Domain) };
let result = match (name, arguments) {
("sqrt", &[x]) => domain(x >= 0.0, sqrt(x)),
("cbrt", &[x]) => Ok(cbrt(x)),
("abs", &[x]) => Ok(x.abs()),
("sign", &[x]) => domain(!x.is_nan(), if x == 0.0 { 0.0 } else { x.signum() }),
("exp", &[x]) => Ok(exp(x)),
("ln", &[x]) => domain(x > 0.0, ln(x)),
("log2", &[x]) => domain(x > 0.0, log2(x)),
("log10" | "log", &[x]) => domain(x > 0.0, log10(x)),
("log", &[x, base]) => domain(x > 0.0 && base > 0.0 && base != 1.0, ln(x) / ln(base)),
("sin", &[x]) => Ok(sin(x)),
("cos", &[x]) => Ok(cos(x)),
("tan", &[x]) => Ok(tan(x)),
("asin", &[x]) => domain((-1.0..=1.0).contains(&x), asin(x)),
("acos", &[x]) => domain((-1.0..=1.0).contains(&x), acos(x)),
("atan", &[x]) => Ok(atan(x)),
("atan2", &[y, x]) => Ok(atan2(y, x)),
("sinh", &[x]) => Ok(sinh(x)),
("cosh", &[x]) => Ok(cosh(x)),
("tanh", &[x]) => Ok(tanh(x)),
("asinh", &[x]) => Ok(asinh(x)),
("acosh", &[x]) => domain(x >= 1.0, acosh(x)),
("atanh", &[x]) => domain(x > -1.0 && x < 1.0, atanh(x)),
("floor", &[x]) => Ok(floor(x)),
("ceil", &[x]) => Ok(ceil(x)),
("round", &[x]) => Ok(round(x)),
("trunc", &[x]) => Ok(trunc(x)),
("pow", &[x, y]) => return Some(x.power(y)),
("hypot", &[x, y]) => Ok(hypot(x, y)),
("min", &[first, ref rest @ ..]) => Ok(rest.iter().fold(first, |a, b| a.min(*b))),
("max", &[first, ref rest @ ..]) => Ok(rest.iter().fold(first, |a, b| a.max(*b))),
("clamp", &[x, low, high]) => domain(low <= high, x.max(low).min(high)),
(
"sqrt" | "cbrt" | "abs" | "sign" | "exp" | "ln" | "log2" | "log10" | "log" | "sin" | "cos"
| "tan" | "asin" | "acos" | "atan" | "atan2" | "sinh" | "cosh" | "tanh" | "asinh" | "acosh"
| "atanh" | "floor" | "ceil" | "round" | "trunc" | "pow" | "hypot" | "min" | "max" | "clamp",
_,
) => Err(ErrorKind::ArgumentCount),
_ => return None,
};
Some(result.and_then(|value| {
if !finite(value) && arguments.iter().all(|argument| finite(*argument)) {
Err(if value.is_nan() { ErrorKind::Domain } else { ErrorKind::Overflow })
} else {
Ok(value)
}
}))
}
}
};
}
math!(
f32 in single,
unary: [
sqrt = sqrtf, cbrt = cbrtf, exp = expf, ln = logf, log2 = log2f, log10 = log10f,
sin = sinf, cos = cosf, tan = tanf, asin = asinf, acos = acosf, atan = atanf,
sinh = sinhf, cosh = coshf, tanh = tanhf, asinh = asinhf, acosh = acoshf, atanh = atanhf,
floor = floorf, ceil = ceilf, round = roundf, trunc = truncf,
],
binary: [atan2 = atan2f, hypot = hypotf],
);
math!(
f64 in double,
unary: [
sqrt = sqrt, cbrt = cbrt, exp = exp, ln = log, log2 = log2, log10 = log10,
sin = sin, cos = cos, tan = tan, asin = asin, acos = acos, atan = atan,
sinh = sinh, cosh = cosh, tanh = tanh, asinh = asinh, acosh = acosh, atanh = atanh,
floor = floor, ceil = ceil, round = round, trunc = trunc,
],
binary: [atan2 = atan2, hypot = hypot],
);
pub fn evaluate<N: Number>(input: &str) -> Result<N, Error>
where
Math: Context<N>,
{
Evaluator::<Math>::new(&Math).evaluate(input)
}
#[cfg(feature = "decimal")]
impl<const DIGITS: u32, const MAX_EXPONENT: i32> Context<Decimal<DIGITS, MAX_EXPONENT>> for Math {
fn variable(&self, name: &str) -> Option<Decimal<DIGITS, MAX_EXPONENT>> {
let literal = match name {
"pi" | "PI" => "3.14159265358979323846264338327950288",
"tau" => "6.28318530717958647692528676655900577",
"e" | "E" => "2.71828182845904523536028747135266250",
_ => return None,
};
Number::parse(literal)
}
fn call(
&self,
name: &str,
arguments: &[Decimal<DIGITS, MAX_EXPONENT>],
) -> Option<Result<Decimal<DIGITS, MAX_EXPONENT>, ErrorKind>> {
let result = match (name, arguments) {
("abs", &[x]) => Ok(x.abs()),
("sign", &[x]) if x.is_zero() => Ok(x),
("sign", &[x]) => Decimal::new(if x.is_negative() { -1 } else { 1 }, 0),
("floor", &[x]) => x.floor(),
("ceil", &[x]) => x.ceil(),
("round", &[x]) => x.round(),
("trunc", &[x]) => x.trunc(),
("pow", &[x, y]) => x.power(y),
("min", &[first, ref rest @ ..]) => Ok(rest.iter().fold(first, |a, b| a.min(*b))),
("max", &[first, ref rest @ ..]) => Ok(rest.iter().fold(first, |a, b| a.max(*b))),
("clamp", &[x, low, high]) if low <= high => Ok(x.clamp(low, high)),
("clamp", &[_, _, _]) => Err(ErrorKind::Domain),
_ => {
let mut values = [0.0; 4];
for (value, argument) in values.iter_mut().zip(arguments) {
*value = argument.to_f64();
}
let values = &values[..arguments.len().min(values.len())];
Context::<f64>::call(self, name, values)?.and_then(Decimal::from_f64)
}
};
Some(result)
}
}