smooth 0.3.0

human-readable presentation of numbers
Documentation
/// Extension trait to make a single number human-readable.
pub trait Smooth {
    /// Returns the number rounded to the specified decimals.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use smooth::Smooth;
    /// assert_eq!(1.1111.round_to(2), 1.11);
    /// assert_eq!(9.9999.round_to(2), 10.0);
    /// ```
    #[must_use]
    fn round_to(&self, decimals: u16) -> Self;

    /// Rounds the number to the specified decimals.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use smooth::Smooth;
    /// let mut n = 1.1111;
    /// n.round_to_mut(2);
    ///
    /// assert_eq!(n, 1.11);
    /// ```
    fn round_to_mut(&mut self, decimals: u16);

    /// Returns the formatted, rounded number.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use smooth::Smooth;
    /// assert_eq!(1.0.round_to_str(2), "1");
    /// assert_eq!(1.001.round_to_str(2), "1");
    /// assert_eq!(1.018.round_to_str(2), "1.02");
    /// assert_eq!(1.4242.round_to_str(2), "1.42");
    /// ```
    fn round_to_str(&self, decimals: u16) -> String;

    /// Returns the smoothed number.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use smooth::Smooth;
    /// assert_eq!(0.0.smooth(), 0.0);
    /// assert_eq!(0.009.smooth(), 0.01);
    /// assert_eq!(1.001.smooth(), 1.0);
    /// assert_eq!(10.1.smooth(), 10.1);
    /// assert_eq!(1000.9.smooth(), 1001.0);
    /// ```
    #[must_use]
    fn smooth(&self) -> Self;

    /// Smoothes the number.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use smooth::Smooth;
    /// let mut n = 1.001;
    /// n.smooth_mut();
    ///
    /// assert_eq!(n, 1.0);
    /// ```
    fn smooth_mut(&mut self);

    /// Returns the formatted, smoothed number.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use smooth::Smooth;
    /// assert_eq!(0.0.smooth_str(), "0");
    /// assert_eq!(0.009.smooth_str(), "0.01");
    /// assert_eq!(1.001.smooth_str(), "1");
    /// assert_eq!(10.1.smooth_str(), "10.1");
    /// assert_eq!(1000.9.smooth_str(), "1001");
    /// ```
    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 there can only be positive, truncated values
                #[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() {
        // u16 is enough for 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);
    }
}