use crate::error::{DogeError, DogeResult};
use crate::value::Value;
fn numeric(fname: &str, v: &Value) -> DogeResult<f64> {
match v {
Value::Int(n) => Ok(*n as f64),
Value::Float(f) => Ok(*f),
_ => Err(DogeError::type_error(format!(
"nerd.{fname} needs a number, got {}",
v.describe()
))),
}
}
fn float_to_int(f: f64) -> DogeResult {
if f.is_finite() && f >= i64::MIN as f64 && f < i64::MAX as f64 {
Ok(Value::Int(f as i64))
} else {
Err(DogeError::overflow("the result is outside the Int range"))
}
}
fn round_like(fname: &str, x: &Value, op: impl Fn(f64) -> f64) -> DogeResult {
match x {
Value::Int(n) => Ok(Value::Int(*n)),
Value::Float(f) => float_to_int(op(*f)),
_ => Err(DogeError::type_error(format!(
"nerd.{fname} needs a number, got {}",
x.describe()
))),
}
}
pub fn nerd_abs(x: &Value) -> DogeResult {
match x {
Value::Int(n) => {
if *n < 0 {
n.checked_neg()
.map(Value::Int)
.ok_or_else(|| DogeError::overflow("the result is outside the Int range"))
} else {
Ok(Value::Int(*n))
}
}
Value::Float(f) => Ok(Value::Float(f.abs())),
_ => Err(DogeError::type_error(format!(
"nerd.abs needs a number, got {}",
x.describe()
))),
}
}
pub fn nerd_sqrt(x: &Value) -> DogeResult {
let f = numeric("sqrt", x)?;
if f < 0.0 {
return Err(DogeError::value_error("cannot sqrt a negative number"));
}
Ok(Value::Float(f.sqrt()))
}
pub fn nerd_floor(x: &Value) -> DogeResult {
round_like("floor", x, f64::floor)
}
pub fn nerd_ceil(x: &Value) -> DogeResult {
round_like("ceil", x, f64::ceil)
}
pub fn nerd_round(x: &Value) -> DogeResult {
round_like("round", x, f64::round)
}
pub fn nerd_min(a: &Value, b: &Value) -> DogeResult {
let (x, y) = (numeric("min", a)?, numeric("min", b)?);
Ok(if y < x { b.clone() } else { a.clone() })
}
pub fn nerd_max(a: &Value, b: &Value) -> DogeResult {
let (x, y) = (numeric("max", a)?, numeric("max", b)?);
Ok(if y > x { b.clone() } else { a.clone() })
}
pub fn nerd_pow(a: &Value, b: &Value) -> DogeResult {
match (a, b) {
(Value::Int(base), Value::Int(exp)) if *exp >= 0 => {
let exp = u32::try_from(*exp)
.map_err(|_| DogeError::overflow("the result is outside the Int range"))?;
base.checked_pow(exp)
.map(Value::Int)
.ok_or_else(|| DogeError::overflow("the result is outside the Int range"))
}
_ => {
let (x, y) = (numeric("pow", a)?, numeric("pow", b)?);
Ok(Value::Float(x.powf(y)))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ErrorKind;
#[test]
fn abs_keeps_type_and_overflows_catchably() {
assert!(matches!(nerd_abs(&Value::Int(-5)).unwrap(), Value::Int(5)));
assert!(matches!(nerd_abs(&Value::Float(-2.5)).unwrap(), Value::Float(f) if f == 2.5));
assert_eq!(
nerd_abs(&Value::Int(i64::MIN)).unwrap_err().kind,
ErrorKind::Overflow
);
assert_eq!(
nerd_abs(&Value::str("x")).unwrap_err().kind,
ErrorKind::TypeError
);
}
#[test]
fn sqrt_is_float_and_rejects_negatives() {
assert!(matches!(nerd_sqrt(&Value::Int(16)).unwrap(), Value::Float(f) if f == 4.0));
assert_eq!(
nerd_sqrt(&Value::Int(-1)).unwrap_err().kind,
ErrorKind::ValueError
);
}
#[test]
fn floor_ceil_round_yield_ints() {
assert!(matches!(
nerd_floor(&Value::Float(2.9)).unwrap(),
Value::Int(2)
));
assert!(matches!(
nerd_ceil(&Value::Float(2.1)).unwrap(),
Value::Int(3)
));
assert!(matches!(
nerd_round(&Value::Float(2.5)).unwrap(),
Value::Int(3)
));
assert!(matches!(nerd_floor(&Value::Int(7)).unwrap(), Value::Int(7)));
}
#[test]
fn round_out_of_range_is_overflow() {
assert_eq!(
nerd_floor(&Value::Float(1e300)).unwrap_err().kind,
ErrorKind::Overflow
);
}
#[test]
fn min_max_keep_the_winners_type() {
assert!(
matches!(nerd_min(&Value::Int(3), &Value::Float(2.5)).unwrap(), Value::Float(f) if f == 2.5)
);
assert!(matches!(
nerd_max(&Value::Int(3), &Value::Float(2.5)).unwrap(),
Value::Int(3)
));
assert!(matches!(
nerd_min(&Value::Int(4), &Value::Float(4.0)).unwrap(),
Value::Int(4)
));
}
#[test]
fn pow_is_int_for_int_base_and_overflows_catchably() {
assert!(matches!(
nerd_pow(&Value::Int(2), &Value::Int(10)).unwrap(),
Value::Int(1024)
));
assert!(matches!(
nerd_pow(&Value::Int(2), &Value::Float(0.5)).unwrap(),
Value::Float(_)
));
assert_eq!(
nerd_pow(&Value::Int(10), &Value::Int(100))
.unwrap_err()
.kind,
ErrorKind::Overflow
);
}
}