pub use polydat::numeric::round_numbers::{
ceiling_fibonacci_val, floor_fibonacci_val, floor_pow2, floor_pow10, pick_closest,
positive_finite,
};
#[polydat::polydat_node(category = Math)]
pub(crate) fn floor_base10(x: f64) -> f64 {
if !positive_finite(x) {
return 0.0;
}
floor_pow10(x)
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn ceiling_base10(x: f64) -> f64 {
if !positive_finite(x) {
return 0.0;
}
let lo = floor_pow10(x);
if lo == x { lo } else { lo * 10.0 }
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn closest_base10(x: f64) -> f64 {
if !positive_finite(x) {
return 0.0;
}
let lo = floor_pow10(x);
let hi = if lo == x { lo } else { lo * 10.0 };
pick_closest(x, lo, hi)
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn floor_decade(x: f64) -> f64 {
if !positive_finite(x) {
return 0.0;
}
let base = floor_pow10(x);
(x / base).floor() * base
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn ceiling_decade(x: f64) -> f64 {
if !positive_finite(x) {
return 0.0;
}
let base = floor_pow10(x);
(x / base).ceil() * base
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn closest_decade(x: f64) -> f64 {
if !positive_finite(x) {
return 0.0;
}
let base = floor_pow10(x);
(x / base).round() * base
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn floor_fibonacci(x: f64) -> f64 {
if !positive_finite(x) {
return 0.0;
}
floor_fibonacci_val(x)
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn ceiling_fibonacci(x: f64) -> f64 {
if !positive_finite(x) {
return 0.0;
}
ceiling_fibonacci_val(x)
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn closest_fibonacci(x: f64) -> f64 {
if !positive_finite(x) {
return 0.0;
}
pick_closest(x, floor_fibonacci_val(x), ceiling_fibonacci_val(x))
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn floor_binomial(x: f64) -> f64 {
if !positive_finite(x) {
return 0.0;
}
floor_pow2(x)
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn ceiling_binomial(x: f64) -> f64 {
if !positive_finite(x) {
return 0.0;
}
let lo = floor_pow2(x);
if lo == x { lo } else { lo * 2.0 }
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn closest_binomial(x: f64) -> f64 {
if !positive_finite(x) {
return 0.0;
}
let lo = floor_pow2(x);
let hi = if lo == x { lo } else { lo * 2.0 };
pick_closest(x, lo, hi)
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn round_floor(x: f64, interval: f64) -> f64 {
if !(interval.is_finite() && interval > 0.0) {
return x;
}
(x / interval).floor() * interval
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn round_ceiling(x: f64, interval: f64) -> f64 {
if !(interval.is_finite() && interval > 0.0) {
return x;
}
(x / interval).ceil() * interval
}
#[polydat::polydat_node(category = Math)]
pub(crate) fn round_nearest(x: f64, interval: f64) -> f64 {
if !(interval.is_finite() && interval > 0.0) {
return x;
}
(x / interval).round() * interval
}
#[cfg(test)]
mod tests {
use super::*;
use polydat::ast::{PolydatNode, Value};
fn run1(node: &dyn PolydatNode, x: f64) -> f64 {
let mut out = [Value::None];
node.eval(&[Value::F64(x)], &mut out);
out[0].as_f64()
}
fn run2(node: &dyn PolydatNode, x: f64, interval: f64) -> f64 {
let mut out = [Value::None];
node.eval(&[Value::F64(x), Value::F64(interval)], &mut out);
out[0].as_f64()
}
#[test]
fn base10_vectors() {
assert_eq!(run1(&FloorBase10::new(), 1732.234), 1000.0);
assert_eq!(run1(&CeilingBase10::new(), 1732.0), 10000.0);
assert_eq!(run1(&ClosestBase10::new(), 1732.0), 1000.0);
assert_eq!(run1(&ClosestBase10::new(), 6000.0), 10000.0);
}
#[test]
fn base10_exact_power_is_stable() {
assert_eq!(run1(&FloorBase10::new(), 1000.0), 1000.0);
assert_eq!(run1(&CeilingBase10::new(), 1000.0), 1000.0);
}
#[test]
fn base10_fractional_below_one() {
assert!((run1(&FloorBase10::new(), 0.5) - 0.1).abs() < 1e-12);
}
#[test]
fn decade_vectors() {
assert_eq!(run1(&FloorDecade::new(), 2734.0), 2000.0);
assert_eq!(run1(&CeilingDecade::new(), 2734.0), 3000.0);
assert_eq!(run1(&ClosestDecade::new(), 2734.0), 3000.0);
assert_eq!(run1(&FloorDecade::new(), 1732.0), 1000.0);
assert_eq!(run1(&ClosestDecade::new(), 1732.0), 2000.0);
assert_eq!(run1(&CeilingDecade::new(), 1732.0), 2000.0);
}
#[test]
fn fibonacci_vectors() {
assert_eq!(run1(&FloorFibonacci::new(), 1732.0), 1597.0);
assert_eq!(run1(&CeilingFibonacci::new(), 1732.0), 2584.0);
assert_eq!(run1(&ClosestFibonacci::new(), 1732.0), 1597.0);
}
#[test]
fn fibonacci_floor_below_one_is_zero() {
assert_eq!(run1(&FloorFibonacci::new(), 0.5), 0.0);
}
#[test]
fn fibonacci_exact_member_is_stable() {
assert_eq!(run1(&FloorFibonacci::new(), 1597.0), 1597.0);
assert_eq!(run1(&CeilingFibonacci::new(), 1597.0), 1597.0);
}
#[test]
fn binomial_vectors() {
assert_eq!(run1(&FloorBinomial::new(), 1732.0), 1024.0);
assert_eq!(run1(&CeilingBinomial::new(), 1732.0), 2048.0);
assert_eq!(run1(&ClosestBinomial::new(), 1732.0), 2048.0);
}
#[test]
fn binomial_exact_power_is_stable() {
assert_eq!(run1(&FloorBinomial::new(), 1024.0), 1024.0);
assert_eq!(run1(&CeilingBinomial::new(), 1024.0), 1024.0);
}
#[test]
fn non_positive_inputs_are_zero() {
assert_eq!(run1(&FloorBase10::new(), 0.0), 0.0);
assert_eq!(run1(&FloorBase10::new(), -5.0), 0.0);
assert_eq!(run1(&CeilingBinomial::new(), -1.0), 0.0);
assert_eq!(run1(&ClosestFibonacci::new(), 0.0), 0.0);
assert_eq!(run1(&ClosestDecade::new(), -1000.0), 0.0);
assert_eq!(run1(&FloorBase10::new(), f64::INFINITY), 0.0);
assert_eq!(run1(&FloorBinomial::new(), f64::NAN), 0.0);
}
#[test]
fn round_interval_vectors() {
assert_eq!(run2(&RoundFloor::new(), 1732.0, 500.0), 1500.0);
assert_eq!(run2(&RoundCeiling::new(), 1732.0, 500.0), 2000.0);
assert_eq!(run2(&RoundNearest::new(), 1732.0, 500.0), 1500.0);
assert_eq!(run2(&RoundNearest::new(), 1700.0, 500.0), 1500.0);
assert_eq!(run2(&RoundFloor::new(), 1732.0, 0.0), 1732.0);
}
#[test]
fn round_interval_identity_on_bad_interval() {
assert_eq!(run2(&RoundNearest::new(), 1732.0, -5.0), 1732.0);
assert_eq!(run2(&RoundCeiling::new(), 1732.0, f64::INFINITY), 1732.0);
assert_eq!(run2(&RoundFloor::new(), 1732.0, f64::NAN), 1732.0);
}
#[test]
fn all_fifteen_registered_under_math() {
for name in [
"floor_base10",
"ceiling_base10",
"closest_base10",
"floor_decade",
"ceiling_decade",
"closest_decade",
"floor_fibonacci",
"ceiling_fibonacci",
"closest_fibonacci",
"floor_binomial",
"ceiling_binomial",
"closest_binomial",
"round_floor",
"round_ceiling",
"round_nearest",
] {
let sig = polydat::dsl::registry::lookup(name)
.unwrap_or_else(|| panic!("node '{name}' not registered"));
assert_eq!(
sig.category,
polydat::dsl::registry::FuncCategory::Math,
"node '{name}' registered under wrong category",
);
}
}
}