finite 0.0.1

Floats you can count on.
Documentation
use std::hash::{Hash, Hasher};

macro_rules! finite {
    (@op => $opname:ty, $opnamety:ty, $func:tt, $name:tt, $ty:ty) => {
        impl $opname for $name {
            type Output = Option<$ty>;

            fn $func(self, other: Self) -> Option<$ty> {
                let result = (self.0).$func(other.0);

                if result.is_finite() {
                    Some(result)
                } else {
                    None
                }
            }
        }

        impl $opnamety for $name {
            type Output = $ty;

            fn $func(self, other: $ty) -> $ty {
                (self.0).$func(other)
            }
        }
    };
    ($name:tt, $ty:ty) => (
        finite!(@finish => $name, $ty, stringify!($ty));
    );
    (@finish => $name:tt, $ty:ty, $tyname:expr) => {
        #[doc = "A finite `"]
        #[doc = $tyname]
        #[doc = "`. May not be infinite nor NaN."]
        #[derive(Debug, Clone, Copy)]
        pub struct $name($ty);

        impl $name {
            #[doc = "Create a new finite `"]
            #[doc = $tyname]
            #[doc = "`. Will return `None` if given value is infinite or NaN."]
            pub fn new(n: $ty) -> Option<Self> {
                if n.is_finite() {
                    Some(Self(n))
                } else {
                    None
                }
            }

            #[inline(always)]
            pub fn checked_add(self, other: $ty) -> Option<Self> {
                Self::new(std::ops::Add::add(self, other))
            }

            #[inline(always)]
            pub fn checked_sub(self, other: $ty) -> Option<Self> {
                Self::new(std::ops::Sub::sub(self, other))
            }

            #[inline(always)]
            pub fn checked_mul(self, other: $ty) -> Option<Self> {
                Self::new(std::ops::Mul::mul(self, other))
            }

            #[inline(always)]
            pub fn checked_div(self, other: $ty) -> Option<Self> {
                Self::new(std::ops::Div::div(self, other))
            }
        }

        impl PartialEq for $name {
            fn eq(&self, other: &Self) -> bool {
                self.0 == other.0
            }
        }

        impl PartialEq<$ty> for $name {
            fn eq(&self, other: &$ty) -> bool {
                &self.0 == other
            }
        }

        impl Eq for $name {}

        impl PartialOrd for $name {
            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
                self.0.partial_cmp(&other.0)
            }
        }

        impl PartialOrd<$ty> for $name {
            fn partial_cmp(&self, other: &$ty) -> Option<std::cmp::Ordering> {
                self.0.partial_cmp(other)
            }
        }

        impl Ord for $name {
            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
                self.0.partial_cmp(&other.0).expect("must be finite")
            }
        }

        finite!(@op => std::ops::Add, std::ops::Add<$ty>, add, $name, $ty);
        finite!(@op => std::ops::Sub, std::ops::Sub<$ty>, sub, $name, $ty);
        finite!(@op => std::ops::Div, std::ops::Div<$ty>, div, $name, $ty);
        finite!(@op => std::ops::Mul, std::ops::Mul<$ty>, mul, $name, $ty);

        impl std::convert::TryFrom<$ty> for $name {
            type Error = $crate::TryFromFloatError;

            fn try_from(value: $ty) -> Result<Self, Self::Error> {
                match Self::new(value) {
                    Some(v) => Ok(v),
                    None => Err($crate::TryFromFloatError(value.classify()))
                }
            }
        }
    }
}

pub struct TryFromFloatError(std::num::FpCategory);

finite!(FiniteF32, f32);
finite!(FiniteF64, f64);

impl Hash for FiniteF32 {
    fn hash<H: Hasher>(&self, state: &mut H) {
        state.write_u32(unsafe { std::mem::transmute::<f32, u32>(self.0) });
    }
}

impl Hash for FiniteF64 {
    fn hash<H: Hasher>(&self, state: &mut H) {
        state.write_u64(unsafe { std::mem::transmute::<f64, u64>(self.0) });
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::{f32, f64};

    #[test]
    fn smoke() {
        assert!(FiniteF32::new(1f32).is_some());
        assert!(FiniteF64::new(42f64).is_some());
        assert!(FiniteF32::new(f32::NAN).is_none());
        assert!(FiniteF64::new(f64::NAN).is_none());
        assert!(FiniteF32::new(f32::INFINITY).is_none());
        assert!(FiniteF64::new(f64::INFINITY).is_none());
        assert!(FiniteF32::new(f32::NEG_INFINITY).is_none());
        assert!(FiniteF64::new(f64::NEG_INFINITY).is_none());
    }

    #[test]
    fn cmp32() {
        let finite = FiniteF32::new(1f32).unwrap();
        assert_eq!(finite < 32f32, true);
        assert_eq!(finite == 1f32, true);
        assert_eq!(finite > -1f32, true);

        assert_eq!(finite > f32::NAN, false);
        assert_eq!(finite > f32::NEG_INFINITY, true);
        assert_eq!(finite < f32::INFINITY, true);
    }

    #[test]
    fn cmp64() {
        let finite = FiniteF64::new(1f64).unwrap();
        assert_eq!(finite < 64f64, true);
        assert_eq!(finite == 1f64, true);
        assert_eq!(finite > -1f64, true);

        assert_eq!(finite > f64::NAN, false);
        assert_eq!(finite > f64::NEG_INFINITY, true);
        assert_eq!(finite < f64::INFINITY, true);
    }

    #[test]
    fn add32() {
        let finite = FiniteF32::new(1f32).unwrap();
        assert_eq!(finite + 32f32, 33f32);
        assert_eq!(finite - 32f32, -31f32);
        assert_eq!(finite + f32::INFINITY, f32::INFINITY);
        assert_eq!(finite - f32::INFINITY, f32::NEG_INFINITY);
    }

    #[test]
    fn add64() {
        let finite = FiniteF64::new(1f64).unwrap();
        assert_eq!(finite + 32f64, 33f64);
        assert_eq!(finite - 32f64, -31f64);
        assert_eq!(finite + f64::INFINITY, f64::INFINITY);
        assert!(finite.checked_add(f64::INFINITY).is_none());
        assert_eq!(finite - f64::INFINITY, f64::NEG_INFINITY);
        assert!(finite.checked_sub(f64::INFINITY).is_none());
    }

    #[test]
    fn hash() {
        let mut map = std::collections::HashMap::new();
        let f = FiniteF32::new(32f32).unwrap();
        map.insert(f, "oh yes");
        assert_eq!(map[&f], "oh yes");
    }
}