use crate::eval::coercion::to_number;
use crate::eval::functions::check_arity;
use crate::types::{ErrorKind, Value};
pub fn round_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 2) {
return err;
}
let n = match to_number(args[0].clone()) {
Err(e) => return e,
Ok(v) => v,
};
let digits = if args.len() == 2 {
match to_number(args[1].clone()) {
Err(e) => return e,
Ok(v) => v,
}
} else {
0.0
};
let d = digits.trunc() as i32;
let result = round_half_away(n, d);
if !result.is_finite() {
return Value::Error(ErrorKind::Num);
}
Value::Number(result)
}
pub fn roundup_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 2, 2) {
return err;
}
let n = match to_number(args[0].clone()) {
Err(e) => return e,
Ok(v) => v,
};
let digits = match to_number(args[1].clone()) {
Err(e) => return e,
Ok(v) => v,
};
let d = digits.trunc() as i32;
let result = round_away_from_zero(n, d);
if !result.is_finite() {
return Value::Error(ErrorKind::Num);
}
Value::Number(result)
}
pub fn rounddown_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 2, 2) {
return err;
}
let n = match to_number(args[0].clone()) {
Err(e) => return e,
Ok(v) => v,
};
let digits = match to_number(args[1].clone()) {
Err(e) => return e,
Ok(v) => v,
};
let d = digits.trunc() as i32;
let result = round_toward_zero(n, d);
if !result.is_finite() {
return Value::Error(ErrorKind::Num);
}
Value::Number(result)
}
fn scale(d: i32) -> f64 {
10f64.powi(d)
}
fn round_half_away(n: f64, d: i32) -> f64 {
let s = scale(d);
(n * s).signum() * ((n * s).abs() + 0.5 + 1e-12).floor() / s
}
fn round_away_from_zero(n: f64, d: i32) -> f64 {
let s = scale(d);
(n * s).signum() * ((n * s).abs()).ceil() / s
}
fn round_toward_zero(n: f64, d: i32) -> f64 {
let s = scale(d);
(n * s).signum() * ((n * s).abs()).floor() / s
}
pub fn even_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let n = match to_number(args[0].clone()) {
Err(e) => return e,
Ok(v) => v,
};
if n == 0.0 {
return Value::Number(0.0);
}
let sign = if n > 0.0 { 1.0 } else { -1.0 };
let ceil = n.abs().ceil();
let result = if ceil as i64 % 2 == 0 { ceil } else { ceil + 1.0 };
Value::Number(sign * result)
}
pub fn odd_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let n = match to_number(args[0].clone()) {
Err(e) => return e,
Ok(v) => v,
};
if n == 0.0 {
return Value::Number(1.0);
}
let sign = if n > 0.0 { 1.0 } else { -1.0 };
let ceil = n.abs().ceil();
let result = if ceil as i64 % 2 != 0 { ceil } else { ceil + 1.0 };
Value::Number(sign * result)
}
pub fn mround_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 2, 2) {
return err;
}
let n = match to_number(args[0].clone()) {
Err(e) => return e,
Ok(v) => v,
};
let multiple = match to_number(args[1].clone()) {
Err(e) => return e,
Ok(v) => v,
};
if multiple == 0.0 {
return Value::Number(0.0);
}
if n * multiple < 0.0 {
return Value::Error(ErrorKind::Num);
}
Value::Number((n / multiple).round() * multiple)
}
pub fn trunc_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 2) {
return err;
}
let n = match to_number(args[0].clone()) {
Err(e) => return e,
Ok(v) => v,
};
let digits = if args.len() == 2 {
match to_number(args[1].clone()) {
Err(e) => return e,
Ok(v) => v.trunc() as i32,
}
} else {
0i32
};
let result = round_toward_zero(n, digits);
if !result.is_finite() {
return Value::Error(ErrorKind::Num);
}
Value::Number(result)
}
#[cfg(test)]
mod tests;