all_is_cubes_base/math/
restricted_number.rs

1use core::cmp::Ordering;
2use core::fmt;
3use core::hash;
4use core::iter;
5use core::ops;
6
7use num_traits::{ConstOne as _, ConstZero as _, float::FloatCore};
8use ordered_float::NotNan;
9
10// -------------------------------------------------------------------------------------------------
11
12/// A floating-point number which is not NaN and whose sign bit is positive.
13///
14/// The allowed values consist of positive zero, positive infinity,
15/// and every value in between those. This set of values means that this type:
16///
17/// * Implements [`Eq`] straightforwardly; neither NaN nor signed zeroes
18///   can cause problems with using it as a map key for caching, interning, etc.
19/// * Is closed under multiplication and addition, so unlike [`NotNan`], cannot
20///   cause a panic from uncautious arithmetic.
21///
22/// The permitted values of a `PositiveSign<T>` are a subset of those of a `NotNan<T>`.
23/// (This may not be guaranteed if `T` implements numeric traits in inconsistent ways,
24/// but may be assumed for `f32` and `f64`.)
25///
26/// The arithmetic behavior of `PositiveSign<T>` is *not* identical to `T`.
27/// Specifically, the value of `0. * fXX::INFINITY` is NaN, but the value of
28/// `PositiveSign(0.) * PositiveSign(fXX::INFINITY)` is `PositiveSign(0.)` instead.
29/// Our assumption here is that for the applications where `PositiveSign` is suitable, infinities
30/// are either impossible or arise as “would be finite but it is too large to be represented”,
31/// and do not arise as the reciprocal of zero, and thus we can treat “zero times anything
32/// is zero” as being a more important property than “infinity times anything is infinity”.
33#[derive(Clone, Copy, PartialEq)]
34pub struct PositiveSign<T>(T);
35
36/// A floating-point number which is within the range +0 to +1 (inclusive).
37///
38/// This may be used for alpha blending, reflectance, lerps, and anything else where values
39/// outside the range 0 to 1 is meaningless. It is closed under multiplication.
40///
41/// Because NaN and negative zero are excluded, this type implements [`Eq`] straightforwardly,
42/// and can reliably be used as a map key for caching, interning, etc.
43#[derive(Clone, Copy, PartialEq)]
44pub struct ZeroOne<T>(T);
45
46// --- Inherent implementations --------------------------------------------------------------------
47
48impl<T: FloatCore> PositiveSign<T> {
49    /// Construct [`PositiveSign`] without checking the value.
50    ///
51    /// # Safety
52    ///
53    /// `value` must not be NaN and must have a positive sign bit.
54    /// Note that `value >= 0.` is not a sufficient condition,
55    /// because it does not exclude negative zero.
56    #[inline]
57    pub const unsafe fn new_unchecked(value: T) -> Self {
58        Self(value)
59    }
60
61    /// As `Self::new_clamped()` but generic in exchange for not being `const fn`.
62    #[track_caller]
63    #[inline]
64    #[allow(clippy::eq_op)]
65    fn new_clamped_generic_nonconst(value: T) -> Self {
66        if value > T::zero() {
67            Self(value)
68        } else if value == value {
69            Self(T::zero())
70        } else {
71            positive_sign_nan_panic()
72        }
73    }
74
75    /// Unwraps the value without modifying it.
76    #[inline]
77    pub const fn into_inner(self) -> T {
78        self.0
79    }
80
81    pub(crate) const fn into_nn(self) -> NotNan<T> {
82        // SAFETY: `PositiveSign`’s restrictions are a superset of `NotNan`’s.
83        unsafe { NotNan::new_unchecked(self.0) }
84    }
85
86    /// Returns the largest integer less than or equal to `self`.
87    #[inline]
88    #[must_use]
89    pub fn floor(self) -> Self {
90        // should never need to clamp, but to be safe...
91        Self::new_clamped_generic_nonconst(FloatCore::floor(self.0))
92    }
93
94    /// Returns the smallest integer greater than or equal to `self`.
95    #[inline]
96    #[must_use]
97    pub fn ceil(self) -> Self {
98        Self::new_clamped_generic_nonconst(FloatCore::ceil(self.0))
99    }
100
101    /// Returns the nearest integer to `self`.
102    /// If a value is half-way between two integers, round up from 0.0.
103    #[inline]
104    #[must_use]
105    pub fn round(self) -> Self {
106        Self::new_clamped_generic_nonconst(FloatCore::round(self.0))
107    }
108
109    /// Returns whether the value is finite.
110    ///
111    /// Since the value is statically guaranteed to be neither NaN nor negative,
112    /// the only case where this returns `false` is when the value is positive infinity.
113    #[inline]
114    pub fn is_finite(&self) -> bool {
115        // We could delegate to FloatCore::is_finite() but that would perform an unnecessary
116        // NaN check.
117        self.0 != T::infinity()
118    }
119
120    /// Convert to [`ZeroOne`], replacing too-large values with 1.0.
121    #[inline]
122    pub fn clamp_01(self) -> ZeroOne<T> {
123        let one = T::one();
124        if self.0 < one {
125            ZeroOne(self.0)
126        } else {
127            ZeroOne(one)
128        }
129    }
130
131    #[cfg(test)]
132    #[track_caller]
133    pub(crate) fn consistency_check(self)
134    where
135        Self: fmt::Debug,
136    {
137        assert!(!self.0.is_nan() && self.0.is_sign_positive(), "{self:?}");
138    }
139}
140
141impl<T: FloatCore> ZeroOne<T> {
142    /// Construct [`ZeroOne`] without checking the value.
143    ///
144    /// # Safety
145    ///
146    /// `value` must be +0, 1, or some value in between those two.
147    /// Note that `value >= 0. && value <= 1.` is not a sufficient condition,
148    /// because it does not exclude negative zero.
149    #[inline]
150    pub const unsafe fn new_unchecked(value: T) -> Self {
151        Self(value)
152    }
153
154    /// Unwraps the value without modifying it.
155    #[inline]
156    pub const fn into_inner(self) -> T {
157        self.0
158    }
159
160    pub(crate) const fn into_nn(self) -> NotNan<T> {
161        // SAFETY: `ZeroOne`’s restrictions are a superset of `NotNan`’s.
162        unsafe { NotNan::new_unchecked(self.0) }
163    }
164
165    pub(crate) const fn into_ps(self) -> PositiveSign<T> {
166        // Construction safety:
167        // Valid `PositiveSign` values are a superset of valid `ZeroOne` values.
168        PositiveSign(self.0)
169    }
170
171    #[cfg(test)]
172    #[track_caller]
173    pub(crate) fn consistency_check(self)
174    where
175        Self: fmt::Debug,
176    {
177        assert!(
178            self.0.is_sign_positive() && self.0 >= T::zero() && self.0 <= T::one(),
179            "{self:?}"
180        );
181    }
182
183    /// Returns `1.0 - self`.
184    ///
185    /// The result cannot be out of range, so this operation is always successful.
186    #[inline]
187    #[must_use]
188    pub fn complement(self) -> Self {
189        // Construction safety:
190        // `self.0` is at most 1.0, so the result cannot be less than 0.0.
191        // `self.0` is at least 0.0, so the result cannot be greater than 1.0.
192        Self(T::one() - self.0)
193    }
194}
195
196impl<T: FloatCore + num_traits::ConstZero + num_traits::ConstOne> ZeroOne<T> {
197    /// The number zero, as a constant.
198    // This cannot be a `ConstZero` implementation because `ZeroOne` does not implement `ops::Add`.
199    pub const ZERO: Self = Self(T::ZERO);
200    /// The number one, as a constant.
201    // This exists too just for symmetry, though there is a `ConstOne` implementation
202    pub const ONE: Self = Self(T::ONE);
203}
204
205// --- Non-generic macro-generated implementations -------------------------------------------------
206//
207// As it becomes possible, we should replace these with generic impls.
208
209macro_rules! non_generic_impls {
210    ($t:ty) => {
211        impl PositiveSign<$t> {
212            /// Positive infinity (∞).
213            pub const INFINITY: Self = Self(<$t>::INFINITY);
214
215            /// Wraps the given value in `PositiveSign`.
216            ///
217            /// * If `value` is positive (including positive infinity), returns wrapped `value`.
218            /// * If `value` is zero of either sign, returns wrapped positive zero.
219            ///   This is lossy, but corresponds to the IEEE 754 idea that -0.0 == +0.0.
220            /// * If `value` is negative non-zero or NaN, panics.
221            #[track_caller]
222            #[inline]
223            pub const fn new_strict(value: $t) -> Self {
224                if value > 0. {
225                    Self(value)
226                } else if value == 0. {
227                    Self(0.)
228                } else {
229                    positive_sign_not_positive_panic()
230                }
231            }
232
233            /// Wraps the given value in `PositiveSign`.
234            ///
235            /// * If the value is NaN, panics.
236            /// * If the value has a negative sign, it is replaced with positive zero.
237            #[track_caller]
238            #[inline]
239            pub const fn new_clamped(value: $t) -> Self {
240                if value > 0. {
241                    Self(value)
242                } else if value == value {
243                    Self(0.)
244                } else {
245                    positive_sign_nan_panic()
246                }
247            }
248
249            /// Const equivalent of `TryFrom::try_from()`.
250            #[inline]
251            pub(crate) const fn try_new(value: $t) -> Result<Self, NotPositiveSign<$t>> {
252                if value > <$t>::ZERO {
253                    Ok(Self(value))
254                } else if value == <$t>::ZERO {
255                    // must be zero, not NaN, but we don’t know the sign
256                    Ok(Self::ZERO)
257                } else {
258                    Err(NotPositiveSign(value))
259                }
260            }
261
262            /// Subtract `other` from `self`; if the result would be negative, it is zero instead.
263            #[inline]
264            #[must_use]
265            pub fn saturating_sub(self, other: Self) -> Self {
266                // should never be able to panic
267                Self::new_clamped(self.0 - other.0)
268            }
269        }
270
271        impl ZeroOne<$t> {
272            /// Wraps the given value in `ZeroOne`.
273            ///
274            /// * If `value` is in range, returns wrapped `value`.
275            /// * If `value` is zero of either sign, returns wrapped positive zero.
276            ///   This is lossy, but corresponds to the IEEE 754 idea that -0.0 == +0.0.
277            /// * If `value` is out of range or NaN, panics.
278            #[track_caller]
279            #[inline]
280            pub const fn new_strict(value: $t) -> Self {
281                if value > 0. && value <= 1. {
282                    Self(value)
283                } else if value == 0. {
284                    Self(0.)
285                } else {
286                    zero_one_out_of_range_panic()
287                }
288            }
289
290            /// Wraps the given value in `ZeroOne`.
291            ///
292            /// * If the value is NaN, panics.
293            /// * If the value is out of range, replaces it with the nearest in-range value
294            ///   (0 or 1).
295            #[track_caller]
296            #[inline]
297            pub const fn new_clamped(value: $t) -> Self {
298                if value > 0. && value <= 1. {
299                    // note > 0, which excludes negative zero
300                    Self(value)
301                } else if value <= 0. {
302                    Self(0.)
303                } else if value >= 1. {
304                    Self(1.)
305                } else {
306                    zero_one_nan_panic()
307                }
308            }
309
310            /// Const equivalent of `TryFrom::try_from()`.
311            #[inline]
312            pub(crate) const fn try_new(value: $t) -> Result<Self, NotZeroOne<$t>> {
313                if value > <$t>::ZERO && value <= <$t>::ONE {
314                    Ok(Self(value))
315                } else if value == <$t>::ZERO {
316                    // must be zero, not NaN, but we don’t know the sign
317                    Ok(Self(<$t>::ZERO))
318                } else {
319                    Err(NotZeroOne(value))
320                }
321            }
322
323            /// Const version of `self == ZeroOne::ZERO`
324            #[inline]
325            pub const fn is_zero(self) -> bool {
326                self.0 == <$t>::ZERO
327            }
328
329            /// Const version of `self == ZeroOne::ONE`
330            #[inline]
331            pub const fn is_one(self) -> bool {
332                self.0 == <$t>::ONE
333            }
334        }
335
336        impl From<PositiveSign<$t>> for NotNan<$t> {
337            #[inline]
338            fn from(value: PositiveSign<$t>) -> Self {
339                value.into_nn()
340            }
341        }
342        impl From<PositiveSign<$t>> for $t {
343            #[inline]
344            fn from(value: PositiveSign<$t>) -> Self {
345                value.0
346            }
347        }
348        impl From<ZeroOne<$t>> for NotNan<$t> {
349            #[inline]
350            fn from(value: ZeroOne<$t>) -> Self {
351                value.into_nn()
352            }
353        }
354        impl From<ZeroOne<$t>> for $t {
355            #[inline]
356            fn from(value: ZeroOne<$t>) -> Self {
357                value.0
358            }
359        }
360
361        impl From<ZeroOne<$t>> for PositiveSign<$t> {
362            #[inline]
363            fn from(value: ZeroOne<$t>) -> Self {
364                value.into_ps()
365            }
366        }
367
368        impl TryFrom<$t> for PositiveSign<$t> {
369            type Error = NotPositiveSign<$t>;
370
371            /// Checks that `value` is non-negative and non-NaN.
372            ///
373            /// * If `value` is positive (including positive infinity), returns wrapped `value`.
374            /// * If `value` is zero of either sign, returns wrapped positive zero.
375            ///   This is lossy, but corresponds to the IEEE 754 idea that -0.0 == +0.0.
376            /// * If `value` is negative non-zero or NaN, returns an error.
377            #[inline]
378            fn try_from(value: $t) -> Result<Self, Self::Error> {
379                Self::try_new(value)
380            }
381        }
382        impl TryFrom<$t> for ZeroOne<$t> {
383            type Error = NotZeroOne<$t>;
384
385            /// Checks that `value` is within the range 0 to 1.
386            ///
387            /// * If `value` > 0 and `value` ≤ 1, returns wrapped `value`.
388            /// * If `value` is zero of either sign, returns wrapped positive zero.
389            ///   This is lossy, but corresponds to the IEEE 754 idea that -0.0 == +0.0.
390            /// * If `value` is out of range or NaN, returns an error.
391            #[inline]
392            fn try_from(value: $t) -> Result<Self, Self::Error> {
393                Self::try_new(value)
394            }
395        }
396    };
397}
398
399// When f16 and f128 are stable, implement for those too.
400non_generic_impls!(f32);
401non_generic_impls!(f64);
402
403// --- Generic trait implementations ---------------------------------------------------------------
404
405impl<T: fmt::Debug> fmt::Debug for PositiveSign<T> {
406    #[inline(never)]
407    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408        // Don't print the wrappers, just the value.
409        let value: &T = &self.0;
410        value.fmt(f)
411    }
412}
413impl<T: fmt::Display> fmt::Display for PositiveSign<T> {
414    #[inline(never)]
415    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416        // Don't print the wrappers, just the value.
417        let value: &T = &self.0;
418        value.fmt(f)
419    }
420}
421impl<T: fmt::Debug> fmt::Debug for ZeroOne<T> {
422    #[inline(never)]
423    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424        // Don't print the wrappers, just the value.
425        let value: &T = &self.0;
426        value.fmt(f)
427    }
428}
429impl<T: fmt::Display> fmt::Display for ZeroOne<T> {
430    #[inline(never)]
431    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432        // Don't print the wrappers, just the value.
433        let value: &T = &self.0;
434        value.fmt(f)
435    }
436}
437
438// The derived PartialEq implementation is okay, but we need to add Eq.
439impl<T: PartialEq> Eq for PositiveSign<T> {}
440impl<T: PartialEq> Eq for ZeroOne<T> {}
441
442// Allow comparison with the contents.
443// Note: These cannot generalize to `<T: Trait<U>, U> Trait<U> for PositiveSign<T>`,
444// because that would have potential overlap.
445impl<T: PartialEq> PartialEq<T> for PositiveSign<T> {
446    #[inline]
447    fn eq(&self, other: &T) -> bool {
448        self.0 == *other
449    }
450}
451impl<T: PartialEq> PartialEq<T> for ZeroOne<T> {
452    #[inline]
453    fn eq(&self, other: &T) -> bool {
454        self.0 == *other
455    }
456}
457impl<T: PartialOrd> PartialOrd<T> for PositiveSign<T> {
458    #[inline]
459    fn partial_cmp(&self, other: &T) -> Option<Ordering> {
460        self.0.partial_cmp(other)
461    }
462}
463impl<T: PartialOrd> PartialOrd<T> for ZeroOne<T> {
464    #[inline]
465    fn partial_cmp(&self, other: &T) -> Option<Ordering> {
466        self.0.partial_cmp(other)
467    }
468}
469
470// This impl could be derived, but making it explicit will help us ensure it has identical
471// properties to  the `Ord` impl, and quiets `clippy::derive_ord_xor_partial_ord`.
472#[allow(
473    clippy::non_canonical_partial_ord_impl,
474    reason = "that way would have a needless unwrap"
475)]
476impl<T: FloatCore + PartialOrd> PartialOrd for PositiveSign<T> {
477    #[inline]
478    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
479        self.0.partial_cmp(&other.0)
480    }
481}
482impl<T: FloatCore + PartialOrd> Ord for PositiveSign<T> {
483    #[inline]
484    fn cmp(&self, other: &Self) -> Ordering {
485        // Correctness: All values that would violate `Ord`’s properties are prohibited
486        // by the constructors.
487        match self.0.partial_cmp(&other.0) {
488            Some(o) => o,
489            None => invalid_ps_panic(),
490        }
491    }
492}
493// This impl could be derived, but making it explicit will help us ensure it has identical
494// properties to  the `Ord` impl, and quiets `clippy::derive_ord_xor_partial_ord`.
495#[allow(
496    clippy::non_canonical_partial_ord_impl,
497    reason = "that way would have a needless unwrap"
498)]
499impl<T: FloatCore + PartialOrd> PartialOrd for ZeroOne<T> {
500    #[inline]
501    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
502        self.0.partial_cmp(&other.0)
503    }
504}
505impl<T: FloatCore + PartialOrd> Ord for ZeroOne<T> {
506    #[inline]
507    fn cmp(&self, other: &Self) -> Ordering {
508        // Correctness: All values that would violate `Ord`’s properties are prohibited
509        // by the constructors.
510        match self.0.partial_cmp(&other.0) {
511            Some(o) => o,
512            None => invalid_zo_panic(),
513        }
514    }
515}
516
517impl<T: ordered_float::PrimitiveFloat> hash::Hash for PositiveSign<T> {
518    #[inline]
519    fn hash<H: hash::Hasher>(&self, state: &mut H) {
520        self.into_nn().hash(state)
521    }
522}
523impl<T: ordered_float::PrimitiveFloat> hash::Hash for ZeroOne<T> {
524    #[inline]
525    fn hash<H: hash::Hasher>(&self, state: &mut H) {
526        self.into_nn().hash(state)
527    }
528}
529
530impl<T: FloatCore + num_traits::Zero> Default for PositiveSign<T> {
531    /// The default is zero, regardless of what `T::default()` is.
532    #[inline]
533    fn default() -> Self {
534        Self(T::zero())
535    }
536}
537impl<T: FloatCore + num_traits::Zero> Default for ZeroOne<T> {
538    /// The default is zero, regardless of what `T::default()` is.
539    #[inline]
540    fn default() -> Self {
541        Self(T::zero())
542    }
543}
544
545impl<T: FloatCore + num_traits::Zero> num_traits::Zero for PositiveSign<T> {
546    #[inline]
547    fn zero() -> Self {
548        Self(T::zero())
549    }
550
551    #[inline]
552    fn is_zero(&self) -> bool {
553        self.0.is_zero()
554    }
555}
556impl<T: FloatCore + num_traits::One> num_traits::One for PositiveSign<T> {
557    #[inline]
558    fn one() -> Self {
559        Self(T::one())
560    }
561}
562impl<T: FloatCore + num_traits::ConstZero> num_traits::ConstZero for PositiveSign<T> {
563    const ZERO: Self = Self(T::ZERO);
564}
565impl<T: FloatCore + num_traits::ConstOne> num_traits::ConstOne for PositiveSign<T> {
566    const ONE: Self = Self(T::ONE);
567}
568impl<T: FloatCore + num_traits::One> num_traits::One for ZeroOne<T> {
569    #[inline]
570    fn one() -> Self {
571        Self(T::one())
572    }
573}
574impl<T: FloatCore + num_traits::ConstOne> num_traits::ConstOne for ZeroOne<T> {
575    const ONE: Self = Self(T::ONE);
576}
577
578impl<T: FloatCore + ops::Add<Output = T>> ops::Add for PositiveSign<T> {
579    type Output = Self;
580    #[inline]
581    fn add(self, rhs: Self) -> Self::Output {
582        // Construction safety:
583        // If the nonnegative subset of T isn't closed under addition, the number type is
584        // too weird to be useful, and probably doesn’t honestly implement `FloatCore` either.
585        Self(self.0 + rhs.0)
586    }
587}
588impl<T: FloatCore + ops::Add<Output = T>> ops::AddAssign for PositiveSign<T> {
589    #[inline]
590    fn add_assign(&mut self, rhs: Self) {
591        *self = *self + rhs;
592    }
593}
594
595impl<T: FloatCore + ops::Mul<Output = T>> ops::Mul for PositiveSign<T> {
596    type Output = Self;
597
598    /// This multiplication operation differs from standard floating-point multiplication
599    /// in that multiplying zero by positive infinity returns zero instead of NaN.
600    /// This is necessary for the type to be closed under multiplication.
601    #[inline]
602    fn mul(self, rhs: Self) -> Self::Output {
603        let value = self.0 * rhs.0;
604        if value.is_nan() {
605            Self(T::zero())
606        } else {
607            debug_assert!(value.is_sign_positive());
608            Self(value)
609        }
610    }
611}
612impl<T: FloatCore + ops::Mul<Output = T>> ops::Mul for ZeroOne<T> {
613    type Output = Self;
614    #[inline]
615    fn mul(self, rhs: Self) -> Self::Output {
616        // Construction safety:
617        //
618        // If the in-range subset of T isn't closed under multiplication, the number type is
619        // too weird to be useful, and probably doesn’t honestly implement `FloatCore` either.
620        //
621        // In particular, unlike for `PositiveSign`, NaN cannot result because Infinity is out
622        // of range for the arguments.
623        let value = self.0 * rhs.0;
624        Self(value)
625    }
626}
627impl<T: FloatCore + ops::Mul<Output = T>> ops::MulAssign for PositiveSign<T> {
628    #[inline]
629    fn mul_assign(&mut self, rhs: Self) {
630        *self = *self * rhs;
631    }
632}
633impl<T: FloatCore + ops::Mul<Output = T>> ops::MulAssign for ZeroOne<T> {
634    #[inline]
635    fn mul_assign(&mut self, rhs: Self) {
636        *self = *self * rhs;
637    }
638}
639
640// Mixed-type multiplications
641impl<T: FloatCore> ops::Mul<ZeroOne<T>> for PositiveSign<T>
642where
643    PositiveSign<T>: From<ZeroOne<T>>,
644{
645    type Output = PositiveSign<T>;
646    #[inline]
647    fn mul(self, rhs: ZeroOne<T>) -> Self::Output {
648        self * PositiveSign::from(rhs)
649    }
650}
651impl<T: FloatCore> ops::Mul<PositiveSign<T>> for ZeroOne<T>
652where
653    PositiveSign<T>: From<ZeroOne<T>>,
654{
655    type Output = PositiveSign<T>;
656    #[inline]
657    fn mul(self, rhs: PositiveSign<T>) -> Self::Output {
658        PositiveSign::from(self) * rhs
659    }
660}
661
662impl<T: FloatCore + iter::Sum> iter::Sum for PositiveSign<T>
663where
664    Self: TryFrom<T>,
665{
666    #[allow(clippy::missing_inline_in_public_items)]
667    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
668        let inner_sum: T = <T as iter::Sum>::sum(iter.map(PositiveSign::<T>::into_inner));
669        // Note: we could in principle use new_clamped here, but not new_unchecked,
670        // because std `Sum` impls return -0.0 for empty lists!
671        Self::try_from(inner_sum).unwrap_or_else(|_| panic!("sum returned non-positive result"))
672    }
673}
674// cannot implement Sum for ZeroOne, but could implement Product
675
676impl<T> AsRef<T> for PositiveSign<T> {
677    #[inline]
678    fn as_ref(&self) -> &T {
679        &self.0
680    }
681}
682impl<T> AsRef<T> for ZeroOne<T> {
683    #[inline]
684    fn as_ref(&self) -> &T {
685        &self.0
686    }
687}
688
689impl<T> TryFrom<NotNan<T>> for PositiveSign<T>
690where
691    Self: TryFrom<T, Error = NotPositiveSign<T>>,
692{
693    type Error = NotPositiveSign<T>;
694
695    /// Checks that `value` is non-negative.
696    ///
697    /// * If `value` is positive (including positive infinity), returns wrapped `value`.
698    /// * If `value` is zero of either sign, returns wrapped positive zero.
699    ///   This is lossy, but corresponds to the IEEE 754 idea that -0.0 == +0.0.
700    /// * If `value` is negative non-zero or NaN, returns an error.
701    #[inline]
702    fn try_from(value: NotNan<T>) -> Result<Self, Self::Error> {
703        Self::try_from(value.into_inner())
704    }
705}
706
707/// Unsigned integers can be infallibly converted to `PositiveSign`.
708mod integer_to_positive_sign {
709    use super::*;
710    macro_rules! integer_to_positive_sign {
711        ($int:ident, $float:ident) => {
712            impl From<$int> for PositiveSign<$float> {
713                #[inline]
714                fn from(value: $int) -> PositiveSign<$float> {
715                    PositiveSign(value.into())
716                }
717            }
718        };
719    }
720    // This is almost just a list of `T: FloatCore + From<U>`,
721    // but that would be open to weird adversarial implementations.
722    integer_to_positive_sign!(bool, f32);
723    integer_to_positive_sign!(bool, f64);
724    integer_to_positive_sign!(u8, f32);
725    integer_to_positive_sign!(u8, f64);
726    integer_to_positive_sign!(u16, f32);
727    integer_to_positive_sign!(u16, f64);
728    integer_to_positive_sign!(u32, f64);
729}
730
731impl<T: FloatCore> From<bool> for ZeroOne<T> {
732    #[inline]
733    fn from(value: bool) -> Self {
734        if value {
735            ZeroOne(T::one())
736        } else {
737            ZeroOne(T::zero())
738        }
739    }
740}
741
742#[cfg(feature = "arbitrary")]
743#[mutants::skip]
744#[allow(clippy::missing_inline_in_public_items)]
745impl<'a, T> arbitrary::Arbitrary<'a> for PositiveSign<T>
746where
747    NotNan<T>: arbitrary::Arbitrary<'a> + ops::Neg<Output = NotNan<T>> + Copy,
748    Self: TryFrom<NotNan<T>>,
749{
750    #[inline(never)]
751    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
752        let nn = NotNan::<T>::arbitrary(u)?;
753        Self::try_from(nn)
754            .or_else(|_| Self::try_from(-nn))
755            .map_err(|_| unreachable!("all floats are positive or negative"))
756    }
757
758    #[inline(never)]
759    fn size_hint(depth: usize) -> (usize, Option<usize>) {
760        NotNan::<T>::size_hint(depth)
761    }
762
763    #[inline(never)]
764    fn try_size_hint(
765        depth: usize,
766    ) -> arbitrary::Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
767        NotNan::<T>::try_size_hint(depth)
768    }
769}
770#[cfg(feature = "arbitrary")]
771#[mutants::skip]
772#[allow(clippy::missing_inline_in_public_items)]
773impl<'a, T> arbitrary::Arbitrary<'a> for ZeroOne<T>
774where
775    T: FloatCore,
776    NotNan<T>: arbitrary::Arbitrary<'a> + ops::Neg<Output = NotNan<T>> + Copy,
777    Self: TryFrom<T>,
778{
779    #[inline(never)]
780    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
781        let value: T = NotNan::<T>::arbitrary(u)?.into_inner().abs();
782        Self::try_from(value)
783            // if it's greater than 1, try to make it less than 1
784            .or_else(|_| Self::try_from(value.recip()))
785            .map_err(|_| arbitrary::Error::IncorrectFormat)
786    }
787
788    #[inline(never)]
789    fn size_hint(depth: usize) -> (usize, Option<usize>) {
790        NotNan::<T>::size_hint(depth)
791    }
792
793    #[inline(never)]
794    fn try_size_hint(
795        depth: usize,
796    ) -> arbitrary::Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
797        NotNan::<T>::try_size_hint(depth)
798    }
799}
800
801// --- Errors --------------------------------------------------------------------------------------
802
803/// Error from attempting to construct a [`PositiveSign`].
804#[derive(Clone, Debug)]
805pub struct NotPositiveSign<T>(T);
806
807/// Error from attempting to construct a [`ZeroOne`].
808#[derive(Clone, Debug)]
809pub struct NotZeroOne<T>(T);
810
811impl<T: FloatCore + fmt::Display> fmt::Display for NotPositiveSign<T> {
812    #[inline(never)]
813    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
814        let value = self.0;
815        if value.is_nan() {
816            write!(f, "value was NaN")
817        } else {
818            write!(f, "{value} did not have a positive sign bit")
819        }
820    }
821}
822
823impl<T: FloatCore + fmt::Display> fmt::Display for NotZeroOne<T> {
824    #[inline(never)]
825    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
826        let value = self.0;
827        if value <= T::zero() {
828            write!(f, "{value} was less than zero")
829        } else if value >= T::one() {
830            write!(f, "{value} was greater than one")
831        } else {
832            write!(f, "value was NaN")
833        }
834    }
835}
836
837impl<T: FloatCore + fmt::Display + fmt::Debug> core::error::Error for NotPositiveSign<T> {}
838impl<T: FloatCore + fmt::Display + fmt::Debug> core::error::Error for NotZeroOne<T> {}
839
840#[track_caller]
841#[cold]
842const fn positive_sign_nan_panic() -> ! {
843    panic!("PositiveSign value must not be NaN")
844}
845
846#[track_caller]
847#[cold]
848const fn positive_sign_not_positive_panic() -> ! {
849    panic!("PositiveSign value must not be NaN or negative")
850}
851
852#[track_caller]
853#[cold]
854const fn zero_one_nan_panic() -> ! {
855    panic!("ZeroOne value must not be NaN")
856}
857
858#[track_caller]
859#[cold]
860const fn zero_one_out_of_range_panic() -> ! {
861    panic!("ZeroOne value must be between zero and one")
862}
863
864#[track_caller]
865#[cold]
866const fn invalid_zo_panic() -> ! {
867    panic!("an invalid ZeroOne value was encountered; this should be impossible")
868}
869
870#[track_caller]
871#[cold]
872const fn invalid_ps_panic() -> ! {
873    panic!("an invalid PositiveSign value was encountered; this should be impossible")
874}
875
876// -------------------------------------------------------------------------------------------------
877
878/// Convenient alias for [`PositiveSign::<f32>::new_strict()`],
879/// to be used in tests and pseudo-literals.
880#[inline]
881pub const fn ps32(value: f32) -> PositiveSign<f32> {
882    PositiveSign::<f32>::new_strict(value)
883}
884
885/// Convenient alias for [`PositiveSign::<f64>::new_strict()`],
886/// to be used in tests and pseudo-literals.
887#[inline]
888pub const fn ps64(value: f64) -> PositiveSign<f64> {
889    PositiveSign::<f64>::new_strict(value)
890}
891
892/// Convenient alias for [`ZeroOne::<f32>::new_strict()`],
893/// to be used in tests and pseudo-literals.
894#[inline]
895pub const fn zo32(value: f32) -> ZeroOne<f32> {
896    ZeroOne::<f32>::new_strict(value)
897}
898
899/// Convenient alias for [`ZeroOne::<f64>::new_strict()`],
900/// to be used in tests and pseudo-literals.
901#[inline]
902pub const fn zo64(value: f64) -> ZeroOne<f64> {
903    ZeroOne::<f64>::new_strict(value)
904}
905
906// -------------------------------------------------------------------------------------------------
907
908#[cfg(test)]
909#[allow(clippy::manual_range_contains)]
910mod tests {
911    use super::*;
912    use alloc::string::ToString as _;
913    use exhaust::Exhaust as _;
914
915    #[test]
916    fn ps_ord_self() {
917        assert_eq!(ps32(1.0).partial_cmp(&ps32(2.0)), Some(Ordering::Less));
918        assert_eq!(ps32(2.0).partial_cmp(&ps32(2.0)), Some(Ordering::Equal));
919        assert_eq!(ps32(3.0).partial_cmp(&ps32(2.0)), Some(Ordering::Greater));
920        assert_eq!(ps32(1.0).cmp(&ps32(2.0)), Ordering::Less);
921        assert_eq!(ps32(2.0).cmp(&ps32(2.0)), Ordering::Equal);
922        assert_eq!(ps32(3.0).cmp(&ps32(2.0)), Ordering::Greater);
923    }
924
925    #[test]
926    fn zo_ord_self() {
927        assert_eq!(zo32(0.1).partial_cmp(&zo32(0.2)), Some(Ordering::Less));
928        assert_eq!(zo32(0.2).partial_cmp(&zo32(0.2)), Some(Ordering::Equal));
929        assert_eq!(zo32(0.3).partial_cmp(&zo32(0.2)), Some(Ordering::Greater));
930        assert_eq!(zo32(0.1).cmp(&zo32(0.2)), Ordering::Less);
931        assert_eq!(zo32(0.2).cmp(&zo32(0.2)), Ordering::Equal);
932        assert_eq!(zo32(0.3).cmp(&zo32(0.2)), Ordering::Greater);
933    }
934
935    #[test]
936    fn ps_ord_inner() {
937        assert_eq!(ps32(1.0).partial_cmp(&2.0), Some(Ordering::Less));
938        assert_eq!(ps32(2.0).partial_cmp(&2.0), Some(Ordering::Equal));
939        assert_eq!(ps32(3.0).partial_cmp(&2.0), Some(Ordering::Greater));
940        // There is no `Ord` implementation to test.
941
942        // Since the RHS is not a `PositiveSign`, out-of-range values can occur.
943        assert_eq!(ps32(1.0).partial_cmp(&-1.0), Some(Ordering::Greater));
944        assert_eq!(ps32(1.0).partial_cmp(&f32::NAN), None);
945    }
946
947    #[test]
948    fn zo_ord_inner() {
949        assert_eq!(zo32(0.1).partial_cmp(&0.2), Some(Ordering::Less));
950        assert_eq!(zo32(0.2).partial_cmp(&0.2), Some(Ordering::Equal));
951        assert_eq!(zo32(0.3).partial_cmp(&0.2), Some(Ordering::Greater));
952        // There is no `Ord` implementation to test.
953
954        // Since the RHS is not a `ZeroOne`, out-of-range values can occur.
955        assert_eq!(zo32(0.0).partial_cmp(&-1.0), Some(Ordering::Greater));
956        assert_eq!(zo32(1.0).partial_cmp(&2.0), Some(Ordering::Less));
957        assert_eq!(zo32(1.0).partial_cmp(&f32::NAN), None);
958    }
959
960    #[test]
961    fn ps_canonicalizes_negative_zero() {
962        let was_nz = PositiveSign::<f32>::new_strict(-0.0);
963        assert!(was_nz.into_inner().is_sign_positive());
964        assert_eq!(was_nz.to_string(), "0");
965        assert_eq!(was_nz, PositiveSign::<f32>::try_from(-0.0).unwrap())
966    }
967
968    #[test]
969    fn ps_exhaustive() {
970        for f in f32::exhaust() {
971            match PositiveSign::<f32>::try_from(f) {
972                Ok(ps) => {
973                    ps.consistency_check();
974                    assert_eq!(ps, PositiveSign::<f32>::new_clamped(f));
975                    assert_eq!(ps, PositiveSign::<f32>::new_strict(f));
976                    assert_eq!(ps.into_inner(), f);
977                    assert_eq!(f32::from(ps), f);
978                    assert_eq!(NotNan::from(ps), NotNan::new(f).unwrap());
979                }
980                Err(_) => assert!(f.is_nan() || f < 0.0),
981            }
982        }
983    }
984
985    #[test]
986    fn ps_closed_under_multiplication() {
987        assert_eq!(ps32(0.0) * PositiveSign::<f32>::INFINITY, ps32(0.0));
988    }
989
990    #[test]
991    fn ps_floor() {
992        assert_eq!(ps32(0.0).floor(), ps32(0.0));
993        assert_eq!(ps32(0.25).floor(), ps32(0.0));
994        assert_eq!(ps32(0.5).floor(), ps32(0.0));
995        assert_eq!(ps32(0.75).floor(), ps32(0.0));
996        assert_eq!(ps32(1.0).floor(), ps32(1.0));
997        assert_eq!(ps32(1.5).floor(), ps32(1.0));
998        assert_eq!(ps32(7.89).floor(), ps32(7.0));
999    }
1000
1001    #[test]
1002    fn ps_ceil() {
1003        assert_eq!(ps32(0.0).ceil(), ps32(0.0));
1004        assert_eq!(ps32(0.25).ceil(), ps32(1.0));
1005        assert_eq!(ps32(0.5).ceil(), ps32(1.0));
1006        assert_eq!(ps32(0.75).ceil(), ps32(1.0));
1007        assert_eq!(ps32(1.0).ceil(), ps32(1.0));
1008        assert_eq!(ps32(1.5).ceil(), ps32(2.0));
1009        assert_eq!(ps32(7.89).ceil(), ps32(8.0));
1010    }
1011
1012    #[test]
1013    fn ps_round() {
1014        assert_eq!(ps32(0.0).round(), ps32(0.0));
1015        assert_eq!(ps32(0.25).round(), ps32(0.0));
1016        assert_eq!(ps32(0.5).round(), ps32(1.0));
1017        assert_eq!(ps32(0.75).round(), ps32(1.0));
1018        assert_eq!(ps32(1.0).round(), ps32(1.0));
1019        assert_eq!(ps32(1.5).round(), ps32(2.0));
1020        assert_eq!(ps32(7.89).round(), ps32(8.0));
1021    }
1022
1023    #[test]
1024    fn ps_clamp() {
1025        assert_eq!(ps32(0.9).clamp_01(), zo32(0.9));
1026        assert_eq!(ps32(1.0).clamp_01(), zo32(1.0));
1027        assert_eq!(ps32(1.1).clamp_01(), zo32(1.0));
1028    }
1029
1030    #[test]
1031
1032    fn ps_sum() {
1033        assert_eq!(
1034            iter::empty::<PositiveSign<f32>>().sum::<PositiveSign<f32>>(),
1035            ps32(0.0)
1036        );
1037        assert_eq!(
1038            [ps32(0.0)].into_iter().sum::<PositiveSign<f32>>(),
1039            ps32(0.0)
1040        );
1041        assert_eq!(
1042            [ps32(1.0), ps32(2.0)].into_iter().sum::<PositiveSign<f32>>(),
1043            ps32(3.0)
1044        );
1045    }
1046
1047    #[test]
1048    fn zo_canonicalizes_negative_zero() {
1049        let was_nz = ZeroOne::<f32>::new_strict(-0.0);
1050        assert!(was_nz.into_inner().is_sign_positive());
1051        assert_eq!(was_nz.to_string(), "0");
1052        assert_eq!(was_nz, ZeroOne::<f32>::try_from(-0.0).unwrap())
1053    }
1054
1055    #[test]
1056    fn zo_exhaustive() {
1057        for f in f32::exhaust() {
1058            match ZeroOne::<f32>::try_from(f) {
1059                Ok(zo) => {
1060                    zo.consistency_check();
1061                    assert_eq!(zo, ZeroOne::<f32>::new_clamped(f));
1062                    assert_eq!(zo, ZeroOne::<f32>::new_strict(f));
1063                    assert_eq!(zo.into_inner(), f);
1064                    assert_eq!(f32::from(zo), f);
1065                    assert_eq!(NotNan::from(zo), NotNan::new(f).unwrap());
1066                }
1067                Err(_) => assert!(f.is_nan() || f < 0.0 || f > 1.0),
1068            }
1069        }
1070    }
1071
1072    #[test]
1073    fn zo_complement() {
1074        assert_eq!(zo32(0.0).complement(), zo32(1.0));
1075        assert_eq!(zo32(1.0).complement(), zo32(0.0));
1076
1077        // Also check the most extreme cases that aren’t exact
1078        for f in [
1079            f32::from_bits(0x00000001), // next up from 0
1080            f32::from_bits(0x3f7fffff), // next down from 1
1081        ] {
1082            zo32(f).consistency_check();
1083        }
1084    }
1085
1086    #[cfg(feature = "arbitrary")]
1087    #[test]
1088    fn arbitrary_size_hints() {
1089        use arbitrary::Arbitrary as _;
1090
1091        assert_eq!(ZeroOne::<f32>::size_hint(0), (4, Some(4)));
1092        assert_eq!(ZeroOne::<f32>::try_size_hint(0).unwrap(), (4, Some(4)));
1093        assert_eq!(PositiveSign::<f32>::size_hint(0), (4, Some(4)));
1094        assert_eq!(PositiveSign::<f32>::try_size_hint(0).unwrap(), (4, Some(4)));
1095        assert_eq!(ZeroOne::<f64>::size_hint(0), (8, Some(8)));
1096        assert_eq!(ZeroOne::<f64>::try_size_hint(0).unwrap(), (8, Some(8)));
1097        assert_eq!(PositiveSign::<f64>::size_hint(0), (8, Some(8)));
1098        assert_eq!(PositiveSign::<f64>::try_size_hint(0).unwrap(), (8, Some(8)));
1099    }
1100
1101    // TODO: could use some edge-case tests for being closed under arithmetic ops,
1102    // or is that best handled as a fuzz test?
1103}