Skip to content
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
102 changes: 102 additions & 0 deletions typed_floats/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,108 @@ pub trait Powf<T> {
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<f32> = 0.0.try_into().unwrap();
/// let b: PositiveFinite<f32> = a.exclude_inf().unwrap();
/// let c: PositiveFinite<f32> = match a.exclude_inf() {
/// Ok(x) => x,
/// Err(x) => panic!("{} is infinite", x),
/// };
/// ```
fn exclude_inf(self) -> Result<Self::Output, Self>;
}

/// 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<f32> = 1.0.try_into().unwrap();
/// let b: StrictlyPositive<f32> = a.exclude_zero().unwrap();
/// let c: StrictlyPositive<f32> = match a.exclude_zero() {
/// Ok(x) => x,
/// Err(x) => panic!("{} is +/-0.0", x),
/// };
/// ```
fn exclude_zero(self) -> Result<Self::Output, Self>;
}

/// 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<f32> = (-0.0).try_into().unwrap();
/// let b: Negative<f32> = a.exclude_positive().unwrap();
/// let c: Negative<f32> = match a.exclude_positive() {
/// Ok(x) => x,
/// Err(x) => panic!("{} is positive", x),
/// };
/// ```
fn exclude_positive(self) -> Result<Self::Output, Self>;
}

/// 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<f32> = 0.0.try_into().unwrap();
/// let b: Positive<f32> = a.exclude_negative().unwrap();
/// let c: Positive<f32> = match a.exclude_negative() {
/// Ok(x) => x,
/// Err(x) => panic!("{} is negative", x),
/// };
/// ```
fn exclude_negative(self) -> Result<Self::Output, Self>;
}

#[rustversion::since(1.85)]
/// This trait is used to specify the return type of the [`Midpoint::midpoint()`] function.
pub trait Midpoint<T> {
Expand Down
101 changes: 101 additions & 0 deletions typed_floats/src/types/impls/exclude.rs
Original file line number Diff line number Diff line change
@@ -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<f32> {
type Output = $output<f32>;

#[inline]
fn $fn(self) -> Result<Self::Output, Self> {
if
$(self.$check())?
$($crate::$type::accept_zero() && self.0 == $zero)?
{
Err(self)
} else {
Ok(unsafe { $output::<f32>::new_unchecked(self.get()) })
}
}
}

impl $trait for $type<f64> {
type Output = $output<f64>;

#[inline]
fn $fn(self) -> Result<Self::Output, Self> {
if
$(self.$check())?
$($crate::$type::accept_zero() && self.0 == $zero)?
{
Err(self)
} else {
Ok(unsafe { $output::<f64>::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);
1 change: 1 addition & 0 deletions typed_floats/src/types/impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ mod from_str;
mod from_to;
mod hash;
mod ord;
mod exclude;
19 changes: 19 additions & 0 deletions typed_floats/tests/exclude.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use typed_floats::*;

#[test]
fn test_exclude_ok() {
let nonnan: NonNaN<f32> = 1.0.try_into().unwrap();
let positive: Positive<f32> = nonnan.exclude_negative().unwrap();
let finite: PositiveFinite<f32> = positive.exclude_inf().unwrap();
let nonzero: Result<StrictlyPositiveFinite<f32>, PositiveFinite<f32>> = finite.exclude_zero();
assert!(nonzero.is_ok());
}

#[test]
fn test_exclude_err() {
let nonnan: NonNaN<f32> = 0.0.try_into().unwrap();
let positive: Positive<f32> = nonnan.exclude_negative().unwrap();
let finite: PositiveFinite<f32> = positive.exclude_inf().unwrap();
let err: Result<StrictlyPositiveFinite<f32>, PositiveFinite<f32>> = finite.exclude_zero();
assert_eq!(err, Err(finite));
}