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#[derive(Clone, Copy, PartialEq)]
34pub struct PositiveSign<T>(T);
35
36#[derive(Clone, Copy, PartialEq)]
44pub struct ZeroOne<T>(T);
45
46impl<T: FloatCore> PositiveSign<T> {
49 #[inline]
57 pub const unsafe fn new_unchecked(value: T) -> Self {
58 Self(value)
59 }
60
61 #[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 #[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 unsafe { NotNan::new_unchecked(self.0) }
84 }
85
86 #[inline]
88 #[must_use]
89 pub fn floor(self) -> Self {
90 Self::new_clamped_generic_nonconst(FloatCore::floor(self.0))
92 }
93
94 #[inline]
96 #[must_use]
97 pub fn ceil(self) -> Self {
98 Self::new_clamped_generic_nonconst(FloatCore::ceil(self.0))
99 }
100
101 #[inline]
104 #[must_use]
105 pub fn round(self) -> Self {
106 Self::new_clamped_generic_nonconst(FloatCore::round(self.0))
107 }
108
109 #[inline]
114 pub fn is_finite(&self) -> bool {
115 self.0 != T::infinity()
118 }
119
120 #[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 #[inline]
150 pub const unsafe fn new_unchecked(value: T) -> Self {
151 Self(value)
152 }
153
154 #[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 unsafe { NotNan::new_unchecked(self.0) }
163 }
164
165 pub(crate) const fn into_ps(self) -> PositiveSign<T> {
166 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 #[inline]
187 #[must_use]
188 pub fn complement(self) -> Self {
189 Self(T::one() - self.0)
193 }
194}
195
196impl<T: FloatCore + num_traits::ConstZero + num_traits::ConstOne> ZeroOne<T> {
197 pub const ZERO: Self = Self(T::ZERO);
200 pub const ONE: Self = Self(T::ONE);
203}
204
205macro_rules! non_generic_impls {
210 ($t:ty) => {
211 impl PositiveSign<$t> {
212 pub const INFINITY: Self = Self(<$t>::INFINITY);
214
215 #[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 #[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 #[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 Ok(Self::ZERO)
257 } else {
258 Err(NotPositiveSign(value))
259 }
260 }
261
262 #[inline]
264 #[must_use]
265 pub fn saturating_sub(self, other: Self) -> Self {
266 Self::new_clamped(self.0 - other.0)
268 }
269 }
270
271 impl ZeroOne<$t> {
272 #[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 #[track_caller]
296 #[inline]
297 pub const fn new_clamped(value: $t) -> Self {
298 if value > 0. && value <= 1. {
299 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 #[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 Ok(Self(<$t>::ZERO))
318 } else {
319 Err(NotZeroOne(value))
320 }
321 }
322
323 #[inline]
325 pub const fn is_zero(self) -> bool {
326 self.0 == <$t>::ZERO
327 }
328
329 #[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 #[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 #[inline]
392 fn try_from(value: $t) -> Result<Self, Self::Error> {
393 Self::try_new(value)
394 }
395 }
396 };
397}
398
399non_generic_impls!(f32);
401non_generic_impls!(f64);
402
403impl<T: fmt::Debug> fmt::Debug for PositiveSign<T> {
406 #[inline(never)]
407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408 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 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 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 let value: &T = &self.0;
434 value.fmt(f)
435 }
436}
437
438impl<T: PartialEq> Eq for PositiveSign<T> {}
440impl<T: PartialEq> Eq for ZeroOne<T> {}
441
442impl<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#[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 match self.0.partial_cmp(&other.0) {
488 Some(o) => o,
489 None => invalid_ps_panic(),
490 }
491 }
492}
493#[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 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 #[inline]
533 fn default() -> Self {
534 Self(T::zero())
535 }
536}
537impl<T: FloatCore + num_traits::Zero> Default for ZeroOne<T> {
538 #[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 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 #[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 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
640impl<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 Self::try_from(inner_sum).unwrap_or_else(|_| panic!("sum returned non-positive result"))
672 }
673}
674impl<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 #[inline]
702 fn try_from(value: NotNan<T>) -> Result<Self, Self::Error> {
703 Self::try_from(value.into_inner())
704 }
705}
706
707mod 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 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 .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#[derive(Clone, Debug)]
805pub struct NotPositiveSign<T>(T);
806
807#[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#[inline]
881pub const fn ps32(value: f32) -> PositiveSign<f32> {
882 PositiveSign::<f32>::new_strict(value)
883}
884
885#[inline]
888pub const fn ps64(value: f64) -> PositiveSign<f64> {
889 PositiveSign::<f64>::new_strict(value)
890}
891
892#[inline]
895pub const fn zo32(value: f32) -> ZeroOne<f32> {
896 ZeroOne::<f32>::new_strict(value)
897}
898
899#[inline]
902pub const fn zo64(value: f64) -> ZeroOne<f64> {
903 ZeroOne::<f64>::new_strict(value)
904}
905
906#[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 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 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 for f in [
1079 f32::from_bits(0x00000001), f32::from_bits(0x3f7fffff), ] {
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 }