use super::fdlibm;
use super::numbers::as_f64;
use super::*;
pub(super) const MODULE: &str = "pkl.math";
pub(super) fn module() -> Value {
let mut members = ObjectMap::default();
let ints: [(&str, i64); 12] = [
("minInt", i64::MIN),
("minInt8", i64::from(i8::MIN)),
("minInt16", i64::from(i16::MIN)),
("minInt32", i64::from(i32::MIN)),
("maxInt", i64::MAX),
("maxInt8", i64::from(i8::MAX)),
("maxInt16", i64::from(i16::MAX)),
("maxInt32", i64::from(i32::MAX)),
("maxUInt", i64::MAX),
("maxUInt8", i64::from(u8::MAX)),
("maxUInt16", i64::from(u16::MAX)),
("maxUInt32", i64::from(u32::MAX)),
];
for (name, value) in ints {
members.insert(name.into(), Value::Int(value));
}
let floats = [
("minFiniteFloat", -f64::MAX),
("maxFiniteFloat", f64::MAX),
("minPositiveFloat", f64::from_bits(1)),
("e", std::f64::consts::E),
("pi", std::f64::consts::PI),
];
for (name, value) in floats {
members.insert(name.into(), Value::Float(value));
}
typed_object(MODULE, members)
}
fn gcd(mut x: i64, mut y: i64) -> i64 {
while y != 0 {
(x, y) = (y, x % y);
}
x
}
fn number_arg(args: &[Value], index: usize) -> Result<f64> {
as_f64(&args[index]).ok_or_else(|| type_mismatch("Number", &args[index]))
}
fn int_arg(args: &[Value], index: usize) -> Result<i64> {
match &args[index] {
Value::Int(n) => Ok(*n),
other => Err(type_mismatch("Int", other)),
}
}
pub(super) fn call(name: &str, args: &[Value]) -> Option<Result<Value>> {
let unary: Option<fn(f64) -> f64> = match name {
"exp" => Some(fdlibm::exp),
"sqrt" => Some(f64::sqrt),
"cbrt" => Some(fdlibm::cbrt),
"log" => Some(fdlibm::log),
"log2" => Some(|x| fdlibm::log(x) / fdlibm::log(2.0)),
"log10" => Some(fdlibm::log10),
"sin" => Some(fdlibm::sin),
"cos" => Some(fdlibm::cos),
"tan" => Some(fdlibm::tan),
"asin" => Some(fdlibm::asin),
"acos" => Some(fdlibm::acos),
"atan" => Some(fdlibm::atan),
_ => None,
};
if let Some(f) = unary {
return Some(check_arity(args, 1).and_then(|()| Ok(Value::Float(f(number_arg(args, 0)?)))));
}
let arity = match name {
"isPowerOfTwo" => 1,
"atan2" | "gcd" | "lcm" | "min" | "max" => 2,
_ => return None,
};
Some(check_arity(args, arity).and_then(|()| {
Ok(match name {
"atan2" => Value::Float(fdlibm::atan2(number_arg(args, 0)?, number_arg(args, 1)?)),
"gcd" | "lcm" => {
let (x, y) = (int_arg(args, 0)?, int_arg(args, 1)?);
for n in [x, y] {
if n < 0 {
return Err(expected_positive(n));
}
}
if name == "gcd" {
Value::Int(gcd(x, y))
} else if x == 0 || y == 0 {
Value::Int(0)
} else {
(x / gcd(x, y))
.checked_mul(y)
.map(Value::Int)
.ok_or_else(|| Error::Eval("Integer overflow.".into()))?
}
}
"isPowerOfTwo" => Value::Bool(match &args[0] {
Value::Int(n) => *n > 0 && n & (n - 1) == 0,
Value::Float(f) => {
*f > 0.0 && f.is_finite() && {
let bits = f.to_bits();
let significand = bits & ((1 << 52) - 1);
if bits >> 52 == 0 {
significand.is_power_of_two()
} else {
significand == 0
}
}
}
other => return Err(type_mismatch("Number", other)),
}),
_ => {
let (a, b) = (&args[0], &args[1]);
match (a, b) {
(Value::Int(x), Value::Int(y)) => {
Value::Int(if name == "min" { *x.min(y) } else { *x.max(y) })
}
_ => {
let (x, y) = (number_arg(args, 0)?, number_arg(args, 1)?);
Value::Float(java_min_max(name == "min", x, y))
}
}
}
})
}))
}
fn java_min_max(min: bool, x: f64, y: f64) -> f64 {
if x.is_nan() || y.is_nan() {
return f64::NAN;
}
if x == 0.0 && y == 0.0 {
let negative = x.is_sign_negative();
let pick_x = if min { negative } else { !negative };
return if pick_x { x } else { y };
}
if min { x.min(y) } else { x.max(y) }
}