f8 0.2.0

A no_std, one-byte UNORM with exact rounding, saturating arithmetic, and SIMD conversion
Documentation
use crate::f8;
use core::{
    iter::{Product, Sum},
    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
};

impl f8 {
    /// Adds two normalized values, saturating at [`Self::ONE`].
    ///
    /// The result's bits are `min(a + b, 255)`, where `a` and `b` are the
    /// input bits. No rounding is needed.
    ///
    /// # Examples
    ///
    /// ```
    /// use f8::f8;
    ///
    /// let a = f8::from_bits(200);
    /// assert_eq!(a.saturating_add(f8::from_bits(30)).to_bits(), 230);
    /// assert_eq!(a.saturating_add(f8::from_bits(100)), f8::ONE);
    /// ```
    #[inline]
    pub const fn saturating_add(self, rhs: Self) -> Self {
        Self(self.0.saturating_add(rhs.0))
    }

    /// Subtracts two normalized values, saturating at [`Self::ZERO`].
    ///
    /// The result's bits are `max(a - b, 0)`, where `a` and `b` are the
    /// input bits. No rounding is needed.
    ///
    /// # Examples
    ///
    /// ```
    /// use f8::f8;
    ///
    /// let a = f8::from_bits(100);
    /// assert_eq!(a.saturating_sub(f8::from_bits(30)).to_bits(), 70);
    /// assert_eq!(a.saturating_sub(f8::from_bits(200)), f8::ZERO);
    /// ```
    #[inline]
    pub const fn saturating_sub(self, rhs: Self) -> Self {
        Self(self.0.saturating_sub(rhs.0))
    }

    /// Multiplies two normalized values, rounding to the nearest representable value.
    ///
    /// The result's bits are `(a * b + 127) / 255`, using integer division
    /// and `u16` intermediates, where `a` and `b` are the input bits. Since
    /// `255` is odd, exact midpoint ties cannot occur, so this agrees with
    /// nearest-even rounding. The product always lies in `[0, 1]`.
    ///
    /// # Examples
    ///
    /// ```
    /// use f8::f8;
    ///
    /// const PRODUCT: f8 = f8::from_bits(128).saturating_mul(f8::from_bits(128));
    /// assert_eq!(PRODUCT.to_bits(), 64);
    /// assert_eq!(PRODUCT.saturating_mul(f8::ONE), PRODUCT);
    /// ```
    #[inline]
    pub const fn saturating_mul(self, rhs: Self) -> Self {
        Self(((self.0 as u16 * rhs.0 as u16 + 127) / 255) as u8)
    }

    /// Divides two normalized values, rounding to nearest with ties to even
    /// and saturating at [`Self::ONE`].
    ///
    /// For input bits `a` and nonzero `b`, the result's bits are `255 * a / b`,
    /// rounded to the nearest integer (choosing the even integer at a tie)
    /// and clamped to `255`. Zero divided by zero is [`Self::ZERO`]; any
    /// positive value divided by zero is [`Self::ONE`]. This never panics.
    ///
    /// # Examples
    ///
    /// ```
    /// use f8::f8;
    ///
    /// let a = f8::from_bits(1);
    /// assert_eq!(a.saturating_div(f8::from_bits(2)).to_bits(), 128); // 127.5
    /// assert_eq!(a.saturating_div(f8::from_bits(6)).to_bits(), 42); // 42.5
    /// assert_eq!(f8::ONE.saturating_div(a), f8::ONE);
    /// assert_eq!(a.saturating_div(f8::ZERO), f8::ONE);
    /// assert_eq!(f8::ZERO.saturating_div(f8::ZERO), f8::ZERO);
    /// ```
    #[inline]
    pub const fn saturating_div(self, rhs: Self) -> Self {
        if self.0 == 0 {
            return Self::ZERO;
        }
        // This also handles positive values divided by zero.
        if self.0 >= rhs.0 {
            return Self::ONE;
        }

        let numerator = self.0 as u16 * 255;
        let divisor = rhs.0 as u16;
        let quotient = numerator / divisor;
        let twice_remainder = (numerator % divisor) * 2;
        let round_up =
            twice_remainder > divisor || (twice_remainder == divisor && quotient & 1 != 0);
        Self((quotient + round_up as u16) as u8)
    }
}

macro_rules! impl_ops {
    ($trait:ident, $method:ident, $assign_trait:ident, $assign_method:ident, $saturating:ident) => {
        impl $trait for f8 {
            type Output = Self;

            #[inline]
            fn $method(self, rhs: Self) -> Self::Output {
                self.$saturating(rhs)
            }
        }

        impl $trait<&f8> for f8 {
            type Output = f8;

            #[inline]
            fn $method(self, rhs: &f8) -> Self::Output {
                self.$saturating(*rhs)
            }
        }

        impl $trait<f8> for &f8 {
            type Output = f8;

            #[inline]
            fn $method(self, rhs: f8) -> Self::Output {
                (*self).$saturating(rhs)
            }
        }

        impl $trait<&f8> for &f8 {
            type Output = f8;

            #[inline]
            fn $method(self, rhs: &f8) -> Self::Output {
                (*self).$saturating(*rhs)
            }
        }

        impl $assign_trait for f8 {
            #[inline]
            fn $assign_method(&mut self, rhs: Self) {
                *self = self.$saturating(rhs);
            }
        }

        impl $assign_trait<&f8> for f8 {
            #[inline]
            fn $assign_method(&mut self, rhs: &f8) {
                *self = self.$saturating(*rhs);
            }
        }
    };
}

impl_ops!(Add, add, AddAssign, add_assign, saturating_add);
impl_ops!(Sub, sub, SubAssign, sub_assign, saturating_sub);
impl_ops!(Mul, mul, MulAssign, mul_assign, saturating_mul);
impl_ops!(Div, div, DivAssign, div_assign, saturating_div);

/// Sums values in iterator order, saturating at [`f8::ONE`] after each addition.
/// An empty iterator yields [`f8::ZERO`].
impl Sum for f8 {
    #[inline]
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(Self::ZERO, Self::saturating_add)
    }
}

/// Sums borrowed values in iterator order, saturating at [`f8::ONE`] after
/// each addition. An empty iterator yields [`f8::ZERO`].
impl<'a> Sum<&'a f8> for f8 {
    #[inline]
    fn sum<I: Iterator<Item = &'a f8>>(iter: I) -> Self {
        iter.copied().sum()
    }
}

/// Multiplies values in iterator order, starting from [`f8::ONE`].
///
/// Each step saturates to `[0, 1]` and quantizes to nearest-even using
/// [`f8::saturating_mul`] (multiplication has no exact rounding ties).
/// Because intermediate products are quantized, multiplication is not
/// associative, and reordering values can change the result. An empty
/// iterator yields [`f8::ONE`].
///
/// ```
/// use f8::f8;
///
/// let values = [f8::from_bits(1), f8::from_bits(128), f8::from_bits(128)];
/// assert_eq!(values.iter().copied().product::<f8>().to_bits(), 1);
/// assert_eq!(values.iter().rev().copied().product::<f8>(), f8::ZERO);
/// ```
impl Product for f8 {
    #[inline]
    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(Self::ONE, Self::saturating_mul)
    }
}

/// Multiplies borrowed values in iterator order, starting from [`f8::ONE`].
/// Each step saturates to `[0, 1]` and quantizes to nearest-even using
/// [`f8::saturating_mul`], just like the owned implementation. Reordering
/// values can change the result. An empty iterator yields [`f8::ONE`].
impl<'a> Product<&'a f8> for f8 {
    #[inline]
    fn product<I: Iterator<Item = &'a f8>>(iter: I) -> Self {
        iter.copied().product()
    }
}