float_plus 2.0.1

Additional features for float values
Documentation
#![allow(clippy::float_cmp)]

#[cfg(feature = "num")]
use num::complex::{Complex32, Complex64};

use super::round_sf::RoundToSigDig;

pub trait ApproxEqSf {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool;
}

impl ApproxEqSf for f64 {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool {
        let first = self.round_to_sf(significant_figures);
        let second = other.round_to_sf(significant_figures);

        let aeq_sf = first == second;

        aeq_sf
    }
}

impl ApproxEqSf for Option<f64> {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool {
        if self.is_none() && other.is_none() {
            return true;
        }

        if self.is_some() && other.is_some() {
            let Some(first) = self else {
                return false;
            };
            let Some(second) = other else {
                return false;
            };

            let first = first.round_to_sf(significant_figures);
            let second = second.round_to_sf(significant_figures);
            let aeq_sf = first == second;

            return aeq_sf;
        }

        false
    }
}

#[cfg(feature = "num")]
impl ApproxEqSf for Complex64 {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool {
        let real_aeq_sf = self.re.aeq_sf(other.re, significant_figures);
        let imaginary_aeq_sf = self.im.aeq_sf(other.im, significant_figures);
        let aeq_sf = real_aeq_sf && imaginary_aeq_sf;

        aeq_sf
    }
}

#[cfg(feature = "num")]
impl ApproxEqSf for Option<Complex64> {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool {
        if self.is_some() && other.is_some() {
            let first = self.unwrap();
            let second = other.unwrap();
            let real_aeq_sf = first.re.aeq_sf(second.re, significant_figures);
            let imaginary_aeq_sf = first.im.aeq_sf(second.im, significant_figures);
            let aeq_sf = real_aeq_sf && imaginary_aeq_sf;

            return aeq_sf;
        }

        if self.is_none() && other.is_none() {
            return true;
        }

        false
    }
}

#[cfg(feature = "num")]
impl<E> ApproxEqSf for Result<Complex64, E> {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool {
        if self.is_ok() && other.is_ok() {
            let first = unsafe { self.as_ref().unwrap_unchecked() };
            let second = unsafe { other.as_ref().unwrap_unchecked() };
            let real_aeq_sf = first.re.aeq_sf(second.re, significant_figures);
            let imaginary_aeq_sf = first.im.aeq_sf(second.im, significant_figures);
            let aeq_sf = real_aeq_sf && imaginary_aeq_sf;

            return aeq_sf;
        }

        if self.is_err() && other.is_err() {
            return true;
        }

        false
    }
}

impl<E> ApproxEqSf for Result<f64, E> {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool {
        if self.is_err() && other.is_err() {
            return true;
        }

        if self.is_ok() && other.is_ok() {
            let Ok(first) = self else {
                return false;
            };
            let Ok(second) = other else {
                return false;
            };

            let first = first.round_to_sf(significant_figures);
            let second = second.round_to_sf(significant_figures);
            let aeq_sf = first == second;

            return aeq_sf;
        }

        false
    }
}

impl ApproxEqSf for f32 {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool {
        let first = self.round_to_sf(significant_figures);
        let second = other.round_to_sf(significant_figures);

        let aeq = first == second;

        aeq
    }
}

impl ApproxEqSf for Option<f32> {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool {
        if self.is_none() && other.is_none() {
            return true;
        }

        if self.is_some() && other.is_some() {
            let Some(first) = self else {
                return false;
            };
            let Some(second) = other else {
                return false;
            };

            let first = first.round_to_sf(significant_figures);
            let second = second.round_to_sf(significant_figures);
            let aeq_sf = first == second;

            return aeq_sf;
        }

        false
    }
}

impl<E> ApproxEqSf for Result<f32, E> {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool {
        if self.is_err() && other.is_err() {
            return true;
        }

        if self.is_ok() && other.is_ok() {
            let Ok(first) = self else {
                return false;
            };
            let Ok(second) = other else {
                return false;
            };

            let first = first.round_to_sf(significant_figures);
            let second = second.round_to_sf(significant_figures);
            let aeq_sf = first == second;

            return aeq_sf;
        }

        false
    }
}

#[cfg(feature = "num")]
impl ApproxEqSf for Complex32 {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool {
        let real_aeq_sf = self.re.aeq_sf(other.re, significant_figures);
        let imaginary_aeq_sf = self.im.aeq_sf(other.im, significant_figures);
        let aeq_sf = real_aeq_sf && imaginary_aeq_sf;

        aeq_sf
    }
}

#[cfg(feature = "num")]
impl ApproxEqSf for Option<Complex32> {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool {
        if self.is_some() && other.is_some() {
            let first = self.unwrap();
            let second = other.unwrap();
            let real_aeq_sf = first.re.aeq_sf(second.re, significant_figures);
            let imaginary_aeq_sf = first.im.aeq_sf(second.im, significant_figures);
            let aeq_sf = real_aeq_sf && imaginary_aeq_sf;

            return aeq_sf;
        }

        if self.is_none() && other.is_none() {
            return true;
        }

        false
    }
}

#[cfg(feature = "num")]
impl<E> ApproxEqSf for Result<Complex32, E> {
    fn aeq_sf(&self, other: Self, significant_figures: u8) -> bool {
        if self.is_ok() && other.is_ok() {
            let first = unsafe { self.as_ref().unwrap_unchecked() };
            let second = unsafe { other.as_ref().unwrap_unchecked() };
            let real_aeq_sf = first.re.aeq_sf(second.re, significant_figures);
            let imaginary_aeq_sf = first.im.aeq_sf(second.im, significant_figures);
            let aeq_sf = real_aeq_sf && imaginary_aeq_sf;

            return aeq_sf;
        }

        if self.is_err() && other.is_err() {
            return true;
        }

        false
    }
}

#[cfg(test)]
mod tests {
    use anyhow::Error;

    use crate::significant_figures::approx_eq_sf::ApproxEqSf;

    #[test]
    fn aeq_sf_f64() {
        let a = 100.123_456_789_f64;
        let b = 100.123_457;
        assert!(a.aeq_sf(b, 9));

        let a = 100.123_454_789_f64;
        let b = 100.123_457;
        assert!(!a.aeq_sf(b, 9));
    }

    #[test]
    fn aeq_sf_option_f64() {
        let a = Some(100.123_456_789_f64);
        let b = Some(100.123_5_f64);
        assert!(a.aeq_sf(b, 7));

        let a = Some(100.123_456_789_f64);
        let b = None;
        assert!(!a.aeq_sf(b, 7));

        let a = None;
        let b = Some(100.123_5_f64);
        assert!(!a.aeq_sf(b, 7));

        let a = None::<f64>;
        let b = None;
        assert!(a.aeq_sf(b, 7));
    }

    #[test]
    fn aeq_sf_result_f64() {
        let a = Ok::<_, Error>(100.123_456_789_f64);
        let b = Ok(100.123_5_f64);
        assert!(a.aeq_sf(b, 7));

        let a = Ok::<_, Error>(100.123_456_789_f64);
        let b = Err(Error::msg("message"));
        assert!(!a.aeq_sf(b, 7));

        let a = Err(Error::msg("message"));
        let b = Ok::<_, Error>(100.123_5_f64);
        assert!(!a.aeq_sf(b, 7));

        let a = Err::<f64, _>(Error::msg("message"));
        let b = Err(Error::msg("message"));
        assert!(a.aeq_sf(b, 7));
    }

    #[test]
    fn aeq_sf_f32() {
        let a = 100.456_7_f32;
        let b = 100.457;

        assert!(a.aeq_sf(b, 6));

        let a = 100.456_4_f32;
        let b = 100.457;
        assert!(!a.aeq_sf(b, 6));
    }

    #[test]
    fn aeq_sf_option_f32() {
        let a = Some(100.456_7_f32);
        let b = Some(100.5_f32);
        assert!(a.aeq_sf(b, 4));

        let a = Some(100.456_7_f32);
        let b = None;
        assert!(!a.aeq_sf(b, 4));

        let a = None;
        let b = Some(100.5_f32);
        assert!(!a.aeq_sf(b, 4));

        let a = None::<f32>;
        let b = None;
        assert!(a.aeq_sf(b, 4));
    }

    #[test]
    fn aeq_sf_result_f32() {
        let a = Ok::<_, Error>(100.456_7_f32);
        let b = Ok(100.5_f32);
        assert!(a.aeq_sf(b, 4));

        let a = Ok::<_, Error>(100.456_7_f32);
        let b = Err(Error::msg("message"));
        assert!(!a.aeq_sf(b, 4));

        let a = Err(Error::msg("message"));
        let b = Ok::<_, Error>(100.5_f32);
        assert!(!a.aeq_sf(b, 4));

        let a = Err::<f32, _>(Error::msg("message"));
        let b = Err(Error::msg("message"));
        assert!(a.aeq_sf(b, 4));
    }
}