const ROUND_EPSILON: f64 = 1e-9;
fn pow10(digits: i32) -> f64 {
10f64.powi(digits)
}
pub fn excel_round(x: f64, digits: i32) -> f64 {
if !x.is_finite() {
return x;
}
let factor = pow10(digits);
let scaled = x * factor;
let nudged = scaled + scaled.signum() * (scaled.abs() * ROUND_EPSILON);
nudged.round() / factor
}
pub fn excel_roundup(x: f64, digits: i32) -> f64 {
if !x.is_finite() || x == 0.0 {
return x;
}
let factor = pow10(digits);
let scaled = x * factor;
let pulled = scaled - scaled.signum() * (scaled.abs() * ROUND_EPSILON);
let away = if pulled >= 0.0 {
pulled.ceil()
} else {
pulled.floor()
};
away / factor
}
pub fn excel_ceiling(number: f64, significance: f64) -> f64 {
if !number.is_finite() || !significance.is_finite() {
return f64::NAN;
}
if significance == 0.0 {
return 0.0;
}
if number != 0.0 && number.signum() != significance.signum() {
return f64::NAN;
}
let ratio = number / significance;
let pulled = ratio - ratio.signum() * (ratio.abs() * ROUND_EPSILON);
let steps = if pulled >= 0.0 {
pulled.ceil()
} else {
pulled.floor()
};
steps * significance
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_half_away_from_zero_decimal_boundary() {
assert_eq!(excel_round(1594.925, 2), 1594.93);
assert_eq!(excel_round(2.5, 0), 3.0);
assert_eq!(excel_round(-2.5, 0), -3.0); assert_eq!(excel_round(2.4, 0), 2.0);
}
#[test]
fn round_to_negative_digits() {
assert_eq!(excel_round(1234.0, -2), 1200.0);
assert_eq!(excel_round(1250.0, -2), 1300.0);
}
#[test]
fn roundup_is_away_from_zero() {
assert_eq!(excel_roundup(3.001, 2), 3.01);
assert_eq!(excel_roundup(-3.001, 2), -3.01);
assert_eq!(excel_roundup(3.0, 2), 3.0); assert_eq!(excel_roundup(0.0, 2), 0.0);
}
#[test]
fn ceiling_positive_rounds_up_to_multiple() {
assert_eq!(excel_ceiling(10.0, 3.0), 12.0);
assert_eq!(excel_ceiling(12.0, 3.0), 12.0); let req = 666.0_f64; assert_eq!(excel_ceiling(req * 1.05, 50.0), 700.0);
}
#[test]
fn ceiling_negative_magnitude_away_from_zero() {
assert_eq!(excel_ceiling(-10.0, -3.0), -12.0);
assert_eq!(excel_ceiling(-12.0, -3.0), -12.0);
}
#[test]
fn ceiling_zero_significance_is_zero() {
assert_eq!(excel_ceiling(10.0, 0.0), 0.0);
}
#[test]
fn ceiling_sign_mismatch_is_nan() {
assert!(excel_ceiling(10.0, -3.0).is_nan());
assert!(excel_ceiling(-10.0, 3.0).is_nan());
}
}