pub trait Smooth {
#[must_use]
fn round_to(&self, decimals: u16) -> Self;
fn round_to_mut(&mut self, decimals: u16);
fn round_to_str(&self, decimals: u16) -> String;
#[must_use]
fn smooth(&self) -> Self;
fn smooth_mut(&mut self);
fn smooth_str(&self) -> String;
#[doc(hidden)]
fn trunc_digits(&self) -> u16;
}
macro_rules! smooth_impl {
($t:ty) => {
impl Smooth for $t {
fn round_to(&self, decimals: u16) -> Self {
let factor = Self::from(10u16).powi(decimals.into());
(self * factor).round() / factor
}
fn round_to_mut(&mut self, decimals: u16) {
*self = self.round_to(decimals)
}
fn round_to_str(&self, decimals: u16) -> String {
format!("{}", self.round_to(decimals))
}
fn smooth(&self) -> Self {
if self.fract().abs() == 0.0 {
self.round()
} else if self.trunc().abs() == 0.0 {
self.round_to(2)
} else {
let decimals = 3_u16
.checked_sub(self.trunc_digits())
.unwrap_or_default();
self.round_to(decimals)
}
}
fn smooth_mut(&mut self) {
*self = self.smooth()
}
fn smooth_str(&self) -> String {
format!("{}", self.smooth())
}
fn trunc_digits(&self) -> u16 {
let trunc = self.trunc().abs();
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
let ret = (trunc.log10() + 1.0).floor() as u16;
ret
}
}
};
}
smooth_impl!(f32);
smooth_impl!(f64);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trunc_digits() {
assert!(f64::MAX.trunc_digits() < u16::MAX);
assert_eq!(f32::MAX.trunc_digits(), 39);
assert_eq!(f64::MAX.trunc_digits(), 309);
assert_eq!(0.0.trunc_digits(), 0);
assert_eq!(1.0.trunc_digits(), 1);
assert_eq!(10.0.trunc_digits(), 2);
assert_eq!(100.0.trunc_digits(), 3);
assert_eq!(1_000.0.trunc_digits(), 4);
assert_eq!(1_000_000.0.trunc_digits(), 7);
assert_eq!((-1_000.0).trunc_digits(), 4);
assert_eq!((-1_000_000.0).trunc_digits(), 7);
}
}