pub fn round_with_precision(value: f64, precision: i16) -> f64 {
if value.is_infinite()
|| value.is_nan()
|| precision >= 0 && value.abs() >= (1_i64 << f64::MANTISSA_DIGITS) as f64
|| precision >= f64::DIGITS as i16
{
return value;
}
if precision < -(f64::MAX_10_EXP as i16) {
return value * 0.0;
}
if precision > 0 {
let offset = 10_f64.powi(precision.into());
assert!((value * offset).is_finite(), "{value} * {offset} is not finite!");
(value * offset).round() / offset
} else {
let offset = 10_f64.powi((-precision).into());
(value / offset).round() * offset
}
}
pub fn round_int_with_precision(value: i64, precision: i16) -> Option<i64> {
if precision >= 0 {
return Some(value);
}
let digits = -precision as u32;
let Some(ten_to_digits) = 10i64.checked_pow(digits - 1) else {
return Some(0);
};
let truncated = value / ten_to_digits;
if truncated == 0 {
return Some(0);
}
let rounded = if (truncated % 10).abs() >= 5 {
truncated.checked_add(truncated.signum() * (10 - (truncated % 10).abs()))?
} else {
truncated - (truncated % 10)
};
rounded.checked_mul(ten_to_digits)
}
#[cfg(test)]
mod tests {
use super::{round_int_with_precision as rip, round_with_precision as rp};
#[test]
fn test_round_with_precision_0() {
let round = |value| rp(value, 0);
assert_eq!(round(0.0), 0.0);
assert_eq!(round(-0.0), -0.0);
assert_eq!(round(0.4), 0.0);
assert_eq!(round(-0.4), -0.0);
assert_eq!(round(0.56453), 1.0);
assert_eq!(round(-0.56453), -1.0);
}
#[test]
fn test_round_with_precision_1() {
let round = |value| rp(value, 1);
assert_eq!(round(0.0), 0.0);
assert_eq!(round(-0.0), -0.0);
assert_eq!(round(0.4), 0.4);
assert_eq!(round(-0.4), -0.4);
assert_eq!(round(0.44), 0.4);
assert_eq!(round(-0.44), -0.4);
assert_eq!(round(0.56453), 0.6);
assert_eq!(round(-0.56453), -0.6);
assert_eq!(round(0.96453), 1.0);
assert_eq!(round(-0.96453), -1.0);
}
#[test]
fn test_round_with_precision_2() {
let round = |value| rp(value, 2);
assert_eq!(round(0.0), 0.0);
assert_eq!(round(-0.0), -0.0);
assert_eq!(round(0.4), 0.4);
assert_eq!(round(-0.4), -0.4);
assert_eq!(round(0.44), 0.44);
assert_eq!(round(-0.44), -0.44);
assert_eq!(round(0.444), 0.44);
assert_eq!(round(-0.444), -0.44);
assert_eq!(round(0.56553), 0.57);
assert_eq!(round(-0.56553), -0.57);
assert_eq!(round(0.99553), 1.0);
assert_eq!(round(-0.99553), -1.0);
}
#[test]
fn test_round_with_precision_negative_1() {
let round = |value| rp(value, -1);
assert_eq!(round(0.0), 0.0);
assert_eq!(round(-0.0), -0.0);
assert_eq!(round(0.4), 0.0);
assert_eq!(round(-0.4), -0.0);
assert_eq!(round(1234.5), 1230.0);
assert_eq!(round(-1234.5), -1230.0);
assert_eq!(round(1245.232), 1250.0);
assert_eq!(round(-1245.232), -1250.0);
}
#[test]
fn test_round_with_precision_negative_2() {
let round = |value| rp(value, -2);
assert_eq!(round(0.0), 0.0);
assert_eq!(round(-0.0), -0.0);
assert_eq!(round(0.4), 0.0);
assert_eq!(round(-0.4), -0.0);
assert_eq!(round(1243.232), 1200.0);
assert_eq!(round(-1243.232), -1200.0);
assert_eq!(round(1253.232), 1300.0);
assert_eq!(round(-1253.232), -1300.0);
}
#[test]
fn test_round_with_precision_fuzzy() {
let max_int = (1_i64 << f64::MANTISSA_DIGITS) as f64;
let max_digits = f64::DIGITS as i16;
assert_eq!(rp(f64::INFINITY, 0), f64::INFINITY);
assert_eq!(rp(f64::NEG_INFINITY, 0), f64::NEG_INFINITY);
assert!(rp(f64::NAN, 0).is_nan());
assert_eq!(rp(max_int, 0), max_int);
assert_eq!(rp(0.123456, max_digits), 0.123456);
assert_eq!(rp(max_int, max_digits), max_int);
assert_eq!(rp(max_int - 1.0, 0), max_int - 1.0);
assert_eq!(rp(0.123456, max_digits - 1), 0.123456);
assert_eq!(rp(max_int - 1.0, max_digits), max_int - 1.0);
assert_eq!(rp(max_int, max_digits - 1), max_int);
assert_eq!(rp(max_int - 1.0, max_digits - 1), max_int - 1.0);
}
#[test]
fn test_round_with_precision_fuzzy_negative() {
let exp10 = |exponent: i16| 10_f64.powi(exponent.into());
let max_digits = f64::MAX_10_EXP as i16;
let max_up = max_digits + 1;
let max_down = max_digits - 1;
assert_eq!(rp(f64::INFINITY, -1), f64::INFINITY);
assert_eq!(rp(f64::NEG_INFINITY, -1), f64::NEG_INFINITY);
assert!(rp(f64::NAN, -1).is_nan());
assert_eq!(rp(f64::MAX, -max_digits), f64::INFINITY);
assert_eq!(rp(f64::MIN, -max_digits), f64::NEG_INFINITY);
assert_eq!(rp(1.66 * exp10(max_digits), -max_digits), f64::INFINITY);
assert_eq!(rp(-1.66 * exp10(max_digits), -max_digits), f64::NEG_INFINITY);
assert_eq!(rp(1.66 * exp10(max_down), -max_digits), 0.0);
assert_eq!(rp(-1.66 * exp10(max_down), -max_digits), -0.0);
assert_eq!(rp(1234.5678, -max_digits), 0.0);
assert_eq!(rp(-1234.5678, -max_digits), -0.0);
assert_eq!(rp(f64::MAX, -max_up), 0.0);
assert_eq!(rp(f64::MIN, -max_up), -0.0);
assert_eq!(rp(1.66 * exp10(max_digits), -max_up), 0.0);
assert_eq!(rp(-1.66 * exp10(max_digits), -max_up), -0.0);
assert_eq!(rp(1.66 * exp10(max_down), -max_up), 0.0);
assert_eq!(rp(-1.66 * exp10(max_down), -max_up), -0.0);
assert_eq!(rp(1234.5678, -max_up), 0.0);
assert_eq!(rp(-1234.5678, -max_up), -0.0);
assert_eq!(rp(f64::MAX, -max_down), f64::INFINITY);
assert_eq!(rp(f64::MIN, -max_down), f64::NEG_INFINITY);
assert_eq!(rp(1.66 * exp10(max_down), -max_down), 2.0 * exp10(max_down));
assert_eq!(rp(-1.66 * exp10(max_down), -max_down), -2.0 * exp10(max_down));
assert_eq!(rp(1234.5678, -max_down), 0.0);
assert_eq!(rp(-1234.5678, -max_down), -0.0);
assert_eq!(
(rp(1.66 * exp10(max_digits), -max_down) / exp10(max_down)).floor(),
17.0,
);
assert_eq!(
(rp(-1.66 * exp10(max_digits), -max_down) / exp10(max_down)).floor(),
-17.0,
);
}
#[test]
fn test_round_int_with_precision_positive() {
assert_eq!(rip(0, 0), Some(0));
assert_eq!(rip(10, 0), Some(10));
assert_eq!(rip(23, 235), Some(23));
assert_eq!(rip(i64::MAX, 235), Some(i64::MAX));
}
#[test]
fn test_round_int_with_precision_negative_1() {
let round = |value| rip(value, -1);
assert_eq!(round(0), Some(0));
assert_eq!(round(3), Some(0));
assert_eq!(round(5), Some(10));
assert_eq!(round(13), Some(10));
assert_eq!(round(1234), Some(1230));
assert_eq!(round(-1234), Some(-1230));
assert_eq!(round(1245), Some(1250));
assert_eq!(round(-1245), Some(-1250));
assert_eq!(round(i64::MAX), None);
assert_eq!(round(i64::MIN), None);
}
#[test]
fn test_round_int_with_precision_negative_2() {
let round = |value| rip(value, -2);
assert_eq!(round(0), Some(0));
assert_eq!(round(3), Some(0));
assert_eq!(round(5), Some(0));
assert_eq!(round(13), Some(0));
assert_eq!(round(1245), Some(1200));
assert_eq!(round(-1245), Some(-1200));
assert_eq!(round(1253), Some(1300));
assert_eq!(round(-1253), Some(-1300));
assert_eq!(round(i64::MAX), Some(i64::MAX - 7));
assert_eq!(round(i64::MIN), Some(i64::MIN + 8));
}
}