reda-unit 0.1.0

EDA toolkit in Rust
Documentation
mod units;
mod ops;

use core::fmt;
use std::{fmt::Debug, marker::PhantomData, str::FromStr};
use crate::Number;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub use units::*;

pub trait Unit : PartialEq + Eq + Clone + Copy + Debug {
    fn name() -> &'static str;
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnitNumber<U> {
    number: Number,
    unit: PhantomData<U>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnitComplex<U> {
    re: UnitNumber<U>,
    im: UnitNumber<U>,
    unit: PhantomData<U>,
}

impl<U: Unit> UnitNumber<U> {
    pub fn new<N: Into<Number>>(number: N) -> Self {
        Self { number: number.into(), unit: PhantomData }
    }

    pub fn to_f64(&self) -> f64 {
        self.number.to_f64()
    }

    pub fn value(&self) -> Number {
        self.number
    }

    pub fn is_nan(self) -> bool {
        self.number.is_nan()
    }

    pub fn is_finite(self) -> bool {
        self.number.is_finite()
    }

    pub fn powf(self, exp: f64) -> Self {
        Self::new(self.number.powf(exp))
    }

    pub fn atan2(self, other: Self) -> Number {
        self.number.atan2(other.number)
    }
}

macro_rules! impl_f64_like_method {
    ($f:ident) => {
        pub fn $f(&self) -> Self {
            Self::new(self.number.$f())
        }
    };
}

impl<U: Unit> UnitNumber<U> {
    impl_f64_like_method!(abs);
    impl_f64_like_method!(ceil);
    impl_f64_like_method!(floor);
    impl_f64_like_method!(round);
    impl_f64_like_method!(trunc);
    impl_f64_like_method!(fract);

    impl_f64_like_method!(sqrt);
    impl_f64_like_method!(exp);
    impl_f64_like_method!(ln);
    impl_f64_like_method!(log10);
    impl_f64_like_method!(log2);
    impl_f64_like_method!(recip);
    
    impl_f64_like_method!(sin);
    impl_f64_like_method!(cos);
    impl_f64_like_method!(tan);
    impl_f64_like_method!(asin);
    impl_f64_like_method!(acos);
    impl_f64_like_method!(atan);

    impl_f64_like_method!(sinh);
    impl_f64_like_method!(cosh);
    impl_f64_like_method!(tanh);

    impl_f64_like_method!(to_degrees);
    impl_f64_like_method!(to_radians);
}

impl<U: Unit> UnitComplex<U> {
    pub fn new<N1: Into<UnitNumber<U>>, N2: Into<UnitNumber<U>>>(re: N1, im: N2) -> Self {
        Self { re: re.into(), im: im.into(), unit: PhantomData }
    }

    pub fn parts(&self) -> (UnitNumber<U>, UnitNumber<U>) {
        (self.re, self.im)
    }

    pub fn conjugate(&self) -> Self {
        Self {
            re: self.re,
            im: -self.im,
            unit: self.unit
        }
    }

    pub fn abs(&self) -> UnitNumber<U> {
        let abs_value = (self.re.number.powf(2.) + self.im.number.powf(2.)).sqrt();
        UnitNumber::new(abs_value)
    }

    pub fn arg(&self) -> Number {
        self.im.atan2(self.re)
    }
}

impl<U: Unit> fmt::Display for UnitNumber<U> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(p) = f.precision() {
            write!(f, "{:.*}{}", p, self.number, U::name())
        } else {
            write!(f, "{}{}", self.number, U::name())
        }
    }
}

impl<U: Unit> fmt::Display for UnitComplex<U> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(p) = f.precision() {
            write!(f, "{:.*} + {:.*}j", p, self.re, p, self.im)
        } else {
            write!(f, "{} + {}j", self.re, self.im)
        }
    }
}

impl<U: Unit> FromStr for UnitNumber<U> {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s = s.trim();
        if s.ends_with(U::name()) {
            let number_len = s.len() - U::name().len();
            let number_str = &s[..number_len];
            let number: Number = FromStr::from_str(number_str)?;
            Ok(Self::new(number))
        } else {
            Err(format!("Expect end with '{}'", U::name()))
        }
    }
}

impl<U: Unit> Serialize for UnitNumber<U> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = self.to_string();
        serializer.serialize_str(&s)
    }
}

impl<'de, U: Unit> Deserialize<'de> for UnitNumber<U> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Self::from_str(&s).map_err(serde::de::Error::custom)
    }
}

impl<U: Unit> From<Number> for UnitNumber<U> {
    fn from(value: Number) -> Self {
        Self::new(value)
    }
}

macro_rules! impl_from {
    ($t:ty) => {
        impl<U: Unit> From<$t> for UnitNumber<U> {
            fn from(value: $t) -> Self {
                Self::new(Number::from(value as f64))    
            }
        }
    };
}

impl_from!(f64);
impl_from!(f32);
impl_from!(u32);
impl_from!(i32);