const TOP_BIT: u32 = 1 << 31;

/// Compute how different two floats are in ulps.
#[inline(always)]
pub(crate) fn ulp_diff(a: f32, b: f32) -> u32 {
    let a = a.to_bits();
    let b = b.to_bits();

    let a_sign = a & TOP_BIT;
    let b_sign = b & TOP_BIT;
    let a_abs = a & !TOP_BIT;
    let b_abs = b & !TOP_BIT;

    if a_sign == b_sign {
        a_abs.max(b_abs) - a_abs.min(b_abs)
    } else {
        a_abs + b_abs
    }
}

/// Checks if two floats are approximately equal, within `max_ulps`.
#[inline(always)]
pub(crate) fn ulps_eq(a: f32, b: f32, max_ulps: u32) -> bool {
    !a.is_nan() && !b.is_nan() && (ulp_diff(a, b) <= max_ulps)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn ulp_diff_test() {
        assert_eq!(ulp_diff(1.0, 1.0), 0);
        assert_eq!(ulp_diff(0.0, 0.0), 0);
        assert_eq!(ulp_diff(0.0, -0.0), 0);
        assert_eq!(ulp_diff(1.0, 2.0), 1 << 23);
        assert_eq!(ulp_diff(0.0, f32::from_bits(0.0f32.to_bits() + 1)), 1);
        assert_eq!(ulp_diff(-0.0, f32::from_bits(0.0f32.to_bits() + 1)), 1);
        assert_eq!(
            ulp_diff(std::f32::INFINITY, -std::f32::INFINITY),
            0xff000000
        );
    }

    #[test]
    fn ulps_eq_test() {
        assert!(ulps_eq(1.0, 1.0, 0));
        assert!(ulps_eq(1.0, 1.0, 1));
        assert!(ulps_eq(0.0, 0.0, 0));
        assert!(ulps_eq(0.0, -0.0, 0));

        assert!(ulps_eq(1.0, 2.0, 1 << 23));
        assert!(!ulps_eq(1.0, 2.0, (1 << 23) - 1));

        assert!(ulps_eq(0.0, f32::from_bits(0.0f32.to_bits() + 1), 1));
        assert!(!ulps_eq(0.0, f32::from_bits(0.0f32.to_bits() + 1), 0));

        assert!(ulps_eq(-0.0, f32::from_bits(0.0f32.to_bits() + 1), 1));
        assert!(!ulps_eq(-0.0, f32::from_bits(0.0f32.to_bits() + 1), 0));

        assert!(ulps_eq(std::f32::INFINITY, -std::f32::INFINITY, 0xff000000));
        assert!(!ulps_eq(
            std::f32::INFINITY,
            -std::f32::INFINITY,
            0xff000000 - 1
        ));

        assert!(!ulps_eq(std::f32::NAN, std::f32::NAN, 0));
        assert!(!ulps_eq(-std::f32::NAN, -std::f32::NAN, 0));
        assert!(!ulps_eq(std::f32::NAN, std::f32::INFINITY, 1 << 31));
        assert!(!ulps_eq(std::f32::INFINITY, std::f32::NAN, 1 << 31));
    }
}
