yolol_number 0.9.0

A rusty implementation of the weird-ass special number type used in yolol.
Documentation
use num_traits::{
    self,
    Bounded,
    One,
    Zero,
    cast::{
        NumCast,
        AsPrimitive,
    },
};

use crate::traits::{
    YololOps,
    ArgBounds,
};

mod ops;
pub mod conversions;
mod serde_impl;

/// The single canonical definition of how many decimals places exist in a `YololNumber`.
/// At least that's the goal, _most_ of the code uses this, but not all.
const NUMBER_OF_PLACES: u8 = 3;

#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Copy)]
pub struct YololNumber<T: YololOps>(T);

impl<T: YololOps> YololNumber<T>
{
    /// Creates a `YololNumber` with the same value as the input. This will shift the input as necessary.
    /// Does an unchecked `as` cast, so the value may be lossy if misused.
    pub fn from_value(input: impl ArgBounds<T>) -> Self
    {
        let inner = Self::make_inner(input.as_());
        YololNumber(inner).bound()
    }

    /// Creates a `YololNumber` with the input directly used as the raw inner. 
    /// Does an unchecked `as` cast, so the value may be lossy if misused.
    pub fn from_inner(input: impl ArgBounds<T>) -> Self
    {
        YololNumber(input.as_()).bound()
    }

    /// Creates a `YololNumber` from values split into the main digits and decimal digits.
    /// Checks the conversion, so the value is entirely lossless.
    pub fn from_split(main: impl ArgBounds<T>, decimal: impl ArgBounds<T>) -> Option<Self>
    {
        let main = Self::make_inner(T::from(main)?);

        // Clamps the decimal to between -999 and 999, to ensure we don't get weirdness
        let decimal = {
            let val = T::from(decimal)?;
            val % Self::conversion_val()
        };

        Some(YololNumber(main + decimal).bound())
    }

    /// Returns the raw inner value.
    pub fn get_inner(self) -> T
    {
        self.0
    }

    /// Returns the truthy identity.
    pub fn truthy() -> Self
    {
        YololNumber::one()
    }

    /// Returns the falsy identity.
    pub fn falsy() -> Self
    {
        YololNumber::zero()
    }

    /// Clamps the value to the bounds of an expressible `YololNumber`,
    /// regardless of the bounds on the inner type T.
    pub fn bound(self) -> Self
    {
        num_traits::clamp(self, Self::min_value(), Self::max_value())
    }

    /// Returns the value of the number in some other type. Is likely lossy.
    /// Call as `get_value::<T>()` to get the value in a given type T.
    pub fn get_value<F>(self) -> F
    where
        T: AsPrimitive<F>,
        F: 'static + Copy + std::ops::Div<Output=F>
    {
        let inner: F = self.0.as_();
        inner / Self::conversion_val::<F>()
    }

    /// Returns the actual number of decimal places that exist in a yolol number.
    /// Call as `num_places::<T>()` to get the conversion value in a given type T.
    pub fn num_places<F: 'static + Copy>() -> F
    where
        u8: AsPrimitive<F>
    {
        NUMBER_OF_PLACES.as_()
    }

    /// Returns the value used to multiplicatively shift between the raw inner and actual value.
    /// Call as `conversion_val::<T>()` to get the conversion value in a given type T.
    pub fn conversion_val<F: 'static + Copy>() -> F
    where
        T: AsPrimitive<F>
    {
        T::from(10_i64.pow(Self::num_places()))
            .expect("Using YololNumber with a backing type that can't express the conversion factor (10 ^ num_places)!").as_()
    }

    /// Converts a given value to the raw inner that expresses it.
    fn make_inner(num: T) -> T
    {
        num * Self::conversion_val()
    }

    /// Treats the inputs as if it were a raw inner value.
    /// This means it should be larger by a factor of the conversion value than the value you want.
    fn try_to_inner<F: NumCast>(input: F) -> Option<Self>
    {
        // Converts it to T then maps the Some value to YololNumber<T>
        T::from(input)
            .map(YololNumber)
    }

    /// Directly outputs the raw inner value, does not scale it.
    fn try_from_inner<F: NumCast>(&self) -> Option<F>
    {
        F::from(self.0)
    }
}

impl<T: YololOps> num_traits::Zero for YololNumber<T>
{
    /// Returns the value zero.
    fn zero() -> Self
    {
        YololNumber::from_value(T::zero())
    }

    /// Returns whether or not the `YololNumber` is zero.
    fn is_zero(&self) -> bool
    {
        self == &Self::zero()
    }
}

impl<T: YololOps> num_traits::One for YololNumber<T>
{
    /// Returns the value one.
    fn one() -> Self
    {
        YololNumber::from_value(T::one())
    }
}

impl<T: YololOps> num_traits::Num for YololNumber<T>
{
    type FromStrRadixErr = String;

    fn from_str_radix(input: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr>
    {
        if radix != 10
        {
            return Err("Only able to convert from strings in base 10!".to_owned());
        }

        input.parse::<YololNumber<T>>()
            .map_err(|e| e.into())
    }
}

impl<T: YololOps> num_traits::Bounded for YololNumber<T>
{
    /// Returns the minimum value expressible in a `YololNumber`
    fn min_value() -> Self
    {
        let min = T::from(<i64 as num_traits::Bounded>::min_value())
            .unwrap_or_else(T::min_value);

        YololNumber(min)
    }

    /// Returns the maximum value expressible in a `YololNumber`
    fn max_value() -> Self
    {
        let max = T::from(<i64 as num_traits::Bounded>::max_value())
            .unwrap_or_else(T::max_value);

        YololNumber(max)
    }
}