diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3a9fe80..ee47afb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -109,7 +109,7 @@ jobs: steps: - uses: actions/checkout@v4 - - run: cargo install cargo-semver-checks + - run: cargo install cargo-semver-checks --locked - run: cd typed_floats && cargo semver-checks --only-explicit-features ${{ matrix.features }} check-docs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 40ef12b..5f835e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 1.0.7 - Unreleased +### Added + +- `exclude_inf`, `exclude_zero`, `exclude_positive` and `exclude_negative` to restrict a type [#136](https://github.com/tdelmas/typed_floats/pull/136) + ### Fixed - Panic when using `as_const!` with non-const values [#229](https://github.com/tdelmas/typed_floats/pull/229) diff --git a/typed_floats/src/traits.rs b/typed_floats/src/traits.rs index b241e38..7c30af1 100644 --- a/typed_floats/src/traits.rs +++ b/typed_floats/src/traits.rs @@ -191,6 +191,108 @@ pub trait Powf { fn powf(self, rhs: T) -> Self::Output; } +/// This trait is used to specify the return type of the exclude function. +pub trait ExcludeInf: Sized { + /// The resulting type after applying [`ExcludeInf::exclude_inf()`]. + type Output: Sized; + + /// Filters out zeros. + /// + /// # Errors + /// + /// If the value is infinite, it returns an error with the original value. + /// + /// # Examples + /// + /// ``` + /// use typed_floats::*; + /// + /// let a: Positive = 0.0.try_into().unwrap(); + /// let b: PositiveFinite = a.exclude_inf().unwrap(); + /// let c: PositiveFinite = match a.exclude_inf() { + /// Ok(x) => x, + /// Err(x) => panic!("{} is infinite", x), + /// }; + /// ``` + fn exclude_inf(self) -> Result; +} + +/// This trait is used to specify the return type of the exclude function. +pub trait ExcludeZero: Sized { + /// The resulting type after applying [`ExcludeZero::exclude_zero()`]. + type Output: Sized; + + /// Filters out zeros. + /// + /// # Errors + /// + /// If the value is zero, it returns an error with the original value. + /// + /// # Examples + /// + /// ``` + /// use typed_floats::*; + /// + /// let a: Positive = 1.0.try_into().unwrap(); + /// let b: StrictlyPositive = a.exclude_zero().unwrap(); + /// let c: StrictlyPositive = match a.exclude_zero() { + /// Ok(x) => x, + /// Err(x) => panic!("{} is +/-0.0", x), + /// }; + /// ``` + fn exclude_zero(self) -> Result; +} + +/// This trait is used to specify the return type of the exclude function. +pub trait ExcludePositive: Sized { + /// The resulting type after applying [`ExcludePositive::exclude_positive()`]. + type Output: Sized; + + /// Filters out positive values. + /// + /// # Errors + /// + /// If the value is positive, it returns an error with the original value. + /// + /// # Examples + /// + /// ``` + /// use typed_floats::*; + /// let a: NonNaN = (-0.0).try_into().unwrap(); + /// let b: Negative = a.exclude_positive().unwrap(); + /// let c: Negative = match a.exclude_positive() { + /// Ok(x) => x, + /// Err(x) => panic!("{} is positive", x), + /// }; + /// ``` + fn exclude_positive(self) -> Result; +} + +/// This trait is used to specify the return type of the exclude function. +pub trait ExcludeNegative: Sized { + /// The resulting type after applying [`ExcludeNegative::exclude_negative()`]. + type Output: Sized; + + /// Filters out negative values. + /// + /// # Errors + /// + /// If the value is negative, it returns an error with the original value. + /// + /// # Examples + /// + /// ``` + /// use typed_floats::*; + /// let a: NonNaN = 0.0.try_into().unwrap(); + /// let b: Positive = a.exclude_negative().unwrap(); + /// let c: Positive = match a.exclude_negative() { + /// Ok(x) => x, + /// Err(x) => panic!("{} is negative", x), + /// }; + /// ``` + fn exclude_negative(self) -> Result; +} + #[rustversion::since(1.85)] /// This trait is used to specify the return type of the [`Midpoint::midpoint()`] function. pub trait Midpoint { diff --git a/typed_floats/src/types/impls/exclude.rs b/typed_floats/src/types/impls/exclude.rs new file mode 100644 index 0000000..b4b8150 --- /dev/null +++ b/typed_floats/src/types/impls/exclude.rs @@ -0,0 +1,101 @@ +use crate::{ + Negative, NegativeFinite, NonNaN, NonNaNFinite, NonZeroNonNaN, NonZeroNonNaNFinite, Positive, + PositiveFinite, ExcludeInf, ExcludeNegative, ExcludePositive, ExcludeZero, + StrictlyNegative, StrictlyNegativeFinite, StrictlyPositive, StrictlyPositiveFinite, +}; + +macro_rules! impl_exclude { + ($trait:ident, $fn:ident, $($check:ident)? $($zero:literal)?, $type:ident, $output:ident) => { + impl $trait for $type { + type Output = $output; + + #[inline] + fn $fn(self) -> Result { + if + $(self.$check())? + $($crate::$type::accept_zero() && self.0 == $zero)? + { + Err(self) + } else { + Ok(unsafe { $output::::new_unchecked(self.get()) }) + } + } + } + + impl $trait for $type { + type Output = $output; + + #[inline] + fn $fn(self) -> Result { + if + $(self.$check())? + $($crate::$type::accept_zero() && self.0 == $zero)? + { + Err(self) + } else { + Ok(unsafe { $output::::new_unchecked(self.get()) }) + } + } + } + }; +} + +macro_rules! impl_exclude_inf { + ($type:ident, $output:ident) => { + impl_exclude!(ExcludeInf, exclude_inf, is_infinite, $type, $output); + }; +} + +macro_rules! impl_exclude_zero { + ($type:ident, $output:ident) => { + impl_exclude!(ExcludeZero, exclude_zero, 0.0, $type, $output); + }; +} + +macro_rules! impl_exclude_positive { + ($type:ident, $output:ident) => { + impl_exclude!( + ExcludePositive, + exclude_positive, + is_sign_positive, + $type, + $output + ); + }; +} + +macro_rules! impl_exclude_negative { + ($type:ident, $output:ident) => { + impl_exclude!( + ExcludeNegative, + exclude_negative, + is_sign_negative, + $type, + $output + ); + }; +} + +impl_exclude_inf!(NonNaN, NonNaNFinite); +impl_exclude_inf!(NonZeroNonNaN, NonZeroNonNaNFinite); +impl_exclude_inf!(StrictlyPositive, StrictlyPositiveFinite); +impl_exclude_inf!(StrictlyNegative, StrictlyNegativeFinite); +impl_exclude_inf!(Positive, PositiveFinite); +impl_exclude_inf!(Negative, NegativeFinite); + +impl_exclude_zero!(NonNaN, NonZeroNonNaN); +impl_exclude_zero!(NonNaNFinite, NonZeroNonNaNFinite); +impl_exclude_zero!(PositiveFinite, StrictlyPositiveFinite); +impl_exclude_zero!(NegativeFinite, StrictlyNegativeFinite); +impl_exclude_zero!(Positive, StrictlyPositive); +impl_exclude_zero!(Negative, StrictlyNegative); + +impl_exclude_positive!(NonNaN, Negative); +impl_exclude_positive!(NonNaNFinite, NegativeFinite); +impl_exclude_positive!(NonZeroNonNaN, StrictlyNegative); +impl_exclude_positive!(NonZeroNonNaNFinite, StrictlyNegativeFinite); + +impl_exclude_negative!(NonNaN, Positive); +impl_exclude_negative!(NonNaNFinite, PositiveFinite); +impl_exclude_negative!(NonZeroNonNaN, StrictlyPositive); +impl_exclude_negative!(NonZeroNonNaNFinite, StrictlyPositiveFinite); diff --git a/typed_floats/src/types/impls/mod.rs b/typed_floats/src/types/impls/mod.rs index 8dec7f9..40ee3b5 100644 --- a/typed_floats/src/types/impls/mod.rs +++ b/typed_floats/src/types/impls/mod.rs @@ -5,3 +5,4 @@ mod from_str; mod from_to; mod hash; mod ord; +mod exclude; diff --git a/typed_floats/tests/exclude.rs b/typed_floats/tests/exclude.rs new file mode 100644 index 0000000..d5daf60 --- /dev/null +++ b/typed_floats/tests/exclude.rs @@ -0,0 +1,19 @@ +use typed_floats::*; + +#[test] +fn test_exclude_ok() { + let nonnan: NonNaN = 1.0.try_into().unwrap(); + let positive: Positive = nonnan.exclude_negative().unwrap(); + let finite: PositiveFinite = positive.exclude_inf().unwrap(); + let nonzero: Result, PositiveFinite> = finite.exclude_zero(); + assert!(nonzero.is_ok()); +} + +#[test] +fn test_exclude_err() { + let nonnan: NonNaN = 0.0.try_into().unwrap(); + let positive: Positive = nonnan.exclude_negative().unwrap(); + let finite: PositiveFinite = positive.exclude_inf().unwrap(); + let err: Result, PositiveFinite> = finite.exclude_zero(); + assert_eq!(err, Err(finite)); +}