diff --git a/README.md b/README.md index 04d7d70..0717c1e 100644 --- a/README.md +++ b/README.md @@ -41,18 +41,23 @@ numeric primitives are implemented. That is, you can use [`From`] or ### Version 0.3.0 news (unreleased) - * The method [`to_int`] was changed; now its return type is generic. - * The [`Int`] trait implementation for [`bool`] was removed. + * Every fixed-point type now supports coversion to/from all + primitive number types, including checked versions of the + conversions. + * Every fixed-point type now supports comparisons with all + primitive number types. + * Incompatible change: the method [`to_int`] was changed; now its + return type is generic. + * Incompatible change: The [`Int`] trait implementation for [`bool`] + was removed. * The new method [`to_fixed`] was added. * Checked versions of [`to_fixed`] and [`to_int`] were added. * The methods [`from_fixed`][`Int::from_fixed`] and - [`to_fixed`][`Int::to_fixed`], and thier checked versions, were + [`to_fixed`][`Int::to_fixed`], and their checked versions, were added to the [`Int`] trait. * The method [`from_fixed`][`Float::from_fixed`], and the method [`to_fixed`][`Float::to_fixed`] and its checked versions, were added to the [`Float`] trait. - * Comparisons between all fixed-point numbers and all integers are - now supported. [`Float::from_fixed`]: https://docs.rs/fixed/0.3.0/fixed/sealed/trait.Float.html#method.from_fixed [`Float::to_fixed`]: https://docs.rs/fixed/0.3.0/fixed/sealed/trait.Float.html#method.to_fixed @@ -75,7 +80,8 @@ numeric primitives are implemented. That is, you can use [`From`] or ### Version 0.2.0 news (2019-01-29) - * The method [`from_int`] was changed to accept a generic prameter. + * Incompatible change: the method [`from_int`] was changed to accept + a generic prameter. * The new methods [`from_fixed`] and [`from_float`] were added. * Checked versions of [`from_fixed`], [`from_int`] and [`from_float`] were added. diff --git a/RELEASES.md b/RELEASES.md index e1fd756..fc7099e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -8,8 +8,9 @@ as-is, without any warranty. --> Version 0.3.0 (unreleased) ========================== - * The return type of `to_int` is now generic. - * The `Int` trait implementation for [`bool`] was removed. + * Incompatible change: the return type of `to_int` is now generic. + * Incompatible change: the `Int` trait implementation for [`bool`] + was removed. * The new method `to_fixed` was added. * The new methods `checked_to_fixed`, `checked_to_int`, `saturating_to_fixed`, `saturating_to_int`, `wrapping_to_fixed`, @@ -39,13 +40,13 @@ Version 0.2.1 (2019-01-29) Version 0.2.0 (2019-01-29) ========================== + * Incompatible change: The method `from_int` was change to accept a + generic parameter. * The new methods `from_fixed`, `checked_from_fixed`, `saturating_from_fixed`, `wrapping_from_fixed` and `overflowing_from_fixed` were added. - * The old method `from_int` was removed to be replaced. - * The new methods `from_int`, `checked_from_int`, - `saturating_from_int`, `wrapping_from_int` and - `overflowing_from_int` were added. + * The new methods `checked_from_int`, `saturating_from_int`, + `wrapping_from_int` and `overflowing_from_int` were added. * The new methods `from_float`, `checked_from_float`, `saturating_from_float`, `wrapping_from_float` and `overflowing_from_float` were added. diff --git a/src/cmp.rs b/src/cmp.rs index 47dd06a..cdae635 100644 --- a/src/cmp.rs +++ b/src/cmp.rs @@ -15,7 +15,9 @@ use core::cmp::Ordering; use frac::{IsLessOrEqual, True, Unsigned, U128, U16, U32, U64, U8}; -use sealed::{SealedFixed, SealedInt, Widest}; +#[cfg(feature = "f16")] +use half::f16; +use sealed::{SealedFixed, SealedFloat, SealedInt, Widest}; use { FixedI128, FixedI16, FixedI32, FixedI64, FixedI8, FixedU128, FixedU16, FixedU32, FixedU64, FixedU8, @@ -98,30 +100,12 @@ macro_rules! fixed_cmp_fixed { #[inline] fn le(&self, rhs: &$Rhs) -> bool { - !self.gt(rhs) + !rhs.lt(self) } #[inline] fn gt(&self, rhs: &$Rhs) -> bool { - match (self.to_bits().is_negative(), rhs.to_bits().is_negative()) { - (false, true) => return true, - (true, false) => return false, - _ => {} - } - let (rhs_128, dir, overflow) = rhs.to_bits().to_fixed_dir_overflow( - <$Rhs>::FRAC_NBITS as i32, - Self::FRAC_NBITS, - Self::INT_NBITS, - ); - if overflow { - return rhs.to_bits().is_negative(); - } - let rhs_bits = match rhs_128 { - Widest::Unsigned(bits) => bits as ::Bits, - Widest::Negative(bits) => bits as ::Bits, - }; - self.to_bits() > rhs_bits - || (self.to_bits() == rhs_bits && dir == Ordering::Greater) + rhs.lt(self) } #[inline] @@ -170,17 +154,17 @@ macro_rules! fixed_cmp_int { #[inline] fn le(&self, rhs: &$Int) -> bool { - self.le(&rhs.to_repr_fixed()) + !rhs.lt(self) } #[inline] fn gt(&self, rhs: &$Int) -> bool { - self.gt(&rhs.to_repr_fixed()) + rhs.lt(self) } #[inline] fn ge(&self, rhs: &$Int) -> bool { - self.ge(&rhs.to_repr_fixed()) + !self.lt(rhs) } } @@ -200,17 +184,183 @@ macro_rules! fixed_cmp_int { #[inline] fn le(&self, rhs: &$Fix) -> bool { - self.to_repr_fixed().le(rhs) + !rhs.lt(self) } #[inline] fn gt(&self, rhs: &$Fix) -> bool { - self.to_repr_fixed().gt(rhs) + rhs.lt(self) } #[inline] fn ge(&self, rhs: &$Fix) -> bool { - self.to_repr_fixed().ge(rhs) + !self.lt(rhs) + } + } + }; +} + +macro_rules! fixed_cmp_float { + ($Fix:ident($NBits:ident), $Float:ident) => { + impl PartialEq<$Float> for $Fix + where + Frac: Unsigned + IsLessOrEqual<$NBits, Output = True>, + { + #[inline] + fn eq(&self, rhs: &$Float) -> bool { + if !SealedFloat::is_finite(*rhs) { + return false; + } + let (rhs_128, dir, overflow) = + rhs.to_fixed_dir_overflow(Self::FRAC_NBITS, Self::INT_NBITS); + let rhs_bits = match rhs_128 { + Widest::Unsigned(bits) => bits as ::Bits, + Widest::Negative(bits) => bits as ::Bits, + }; + dir == Ordering::Equal && !overflow && rhs_bits == self.to_bits() + } + } + + impl PartialEq<$Fix> for $Float + where + Frac: Unsigned + IsLessOrEqual<$NBits, Output = True>, + { + #[inline] + fn eq(&self, rhs: &$Fix) -> bool { + rhs.eq(self) + } + } + + impl PartialOrd<$Float> for $Fix + where + Frac: Unsigned + IsLessOrEqual<$NBits, Output = True>, + { + #[inline] + fn partial_cmp(&self, rhs: &$Float) -> Option { + if SealedFloat::is_nan(*rhs) { + return None; + } + let rhs_is_neg = SealedFloat::is_sign_negative(*rhs); + if SealedFloat::is_infinite(*rhs) { + return if rhs_is_neg { + Some(Ordering::Greater) + } else { + Some(Ordering::Less) + }; + } + match (self.to_bits().is_negative(), rhs_is_neg) { + (false, true) => return Some(Ordering::Greater), + (true, false) => return Some(Ordering::Less), + _ => {} + } + let (rhs_128, dir, overflow) = + rhs.to_fixed_dir_overflow(Self::FRAC_NBITS, Self::INT_NBITS); + if overflow { + return if rhs_is_neg { + Some(Ordering::Greater) + } else { + Some(Ordering::Less) + }; + } + let rhs_bits = match rhs_128 { + Widest::Unsigned(bits) => bits as ::Bits, + Widest::Negative(bits) => bits as ::Bits, + }; + Some(self.to_bits().cmp(&rhs_bits).then(dir)) + } + + #[inline] + fn lt(&self, rhs: &$Float) -> bool { + if SealedFloat::is_nan(*rhs) { + return false; + } + let rhs_is_neg = SealedFloat::is_sign_negative(*rhs); + if SealedFloat::is_infinite(*rhs) { + return !rhs_is_neg; + } + match (self.to_bits().is_negative(), rhs_is_neg) { + (false, true) => return false, + (true, false) => return true, + _ => {} + } + let (rhs_128, dir, overflow) = + rhs.to_fixed_dir_overflow(Self::FRAC_NBITS, Self::INT_NBITS); + if overflow { + return !rhs_is_neg; + } + let rhs_bits = match rhs_128 { + Widest::Unsigned(bits) => bits as ::Bits, + Widest::Negative(bits) => bits as ::Bits, + }; + let lhs_bits = self.to_bits(); + lhs_bits < rhs_bits || (lhs_bits == rhs_bits && dir == Ordering::Less) + } + + #[inline] + fn le(&self, rhs: &$Float) -> bool { + !SealedFloat::is_nan(*rhs) && !rhs.lt(self) + } + + #[inline] + fn gt(&self, rhs: &$Float) -> bool { + rhs.lt(self) + } + + #[inline] + fn ge(&self, rhs: &$Float) -> bool { + !SealedFloat::is_nan(*rhs) && !self.lt(rhs) + } + } + + impl PartialOrd<$Fix> for $Float + where + Frac: Unsigned + IsLessOrEqual<$NBits, Output = True>, + { + #[inline] + fn partial_cmp(&self, rhs: &$Fix) -> Option { + rhs.partial_cmp(self).map(Ordering::reverse) + } + + #[inline] + fn lt(&self, rhs: &$Fix) -> bool { + if SealedFloat::is_nan(*self) { + return false; + } + let lhs_is_neg = SealedFloat::is_sign_negative(*self); + if SealedFloat::is_infinite(*self) { + return lhs_is_neg; + } + match (lhs_is_neg, rhs.to_bits().is_negative()) { + (false, true) => return false, + (true, false) => return true, + _ => {} + } + let (lhs_128, dir, overflow) = + self.to_fixed_dir_overflow(<$Fix>::FRAC_NBITS, <$Fix>::INT_NBITS); + if overflow { + return lhs_is_neg; + } + let lhs_bits = match lhs_128 { + Widest::Unsigned(bits) => bits as <$Fix as SealedFixed>::Bits, + Widest::Negative(bits) => bits as <$Fix as SealedFixed>::Bits, + }; + let rhs_bits = rhs.to_bits(); + lhs_bits < rhs_bits || (lhs_bits == rhs_bits && dir == Ordering::Greater) + } + + #[inline] + fn le(&self, rhs: &$Fix) -> bool { + !SealedFloat::is_nan(*self) && !rhs.lt(self) + } + + #[inline] + fn gt(&self, rhs: &$Fix) -> bool { + rhs.lt(self) + } + + #[inline] + fn ge(&self, rhs: &$Fix) -> bool { + !SealedFloat::is_nan(*self) && !self.lt(rhs) } } }; @@ -250,6 +400,10 @@ macro_rules! fixed_cmp_all { fixed_cmp_int! { $Fix($NBits), u32 } fixed_cmp_int! { $Fix($NBits), u64 } fixed_cmp_int! { $Fix($NBits), u128 } + #[cfg(feature = "f16")] + fixed_cmp_float! { $Fix($NBits), f16 } + fixed_cmp_float! { $Fix($NBits), f32 } + fixed_cmp_float! { $Fix($NBits), f64 } }; } @@ -280,6 +434,8 @@ fixed_cmp! { FixedI64(i64, U64, 64) } fixed_cmp! { FixedI128(i128, U128, 128) } #[cfg(test)] +#[cfg_attr(feature = "cargo-clippy", allow(clippy::float_cmp))] +#[cfg_attr(feature = "cargo-clippy", allow(clippy::cyclomatic_complexity))] mod tests { use *; @@ -302,6 +458,16 @@ mod tests { assert!(a.eq(&b) && b.eq(&a)); assert_eq!(a.partial_cmp(&b), Some(Equal)); assert_eq!(b.partial_cmp(&a), Some(Equal)); + assert!(a < 0.0); + assert_eq!(a, -(-16f32).exp2()); + assert!(a <= -(-16f32).exp2()); + assert!(a >= -(-16f32).exp2()); + assert!(a < (-16f32).exp2()); + assert_ne!(a, -0.75 * (-16f32).exp2()); + assert!(a < -0.75 * (-16f32).exp2()); + assert!(a <= -0.75 * (-16f32).exp2()); + assert!(a > -1.25 * (-16f32).exp2()); + assert!(a >= -1.25 * (-16f32).exp2()); a >>= 1; b >>= 1; // a = ffff.ffff = -2^-16, b = fff.ffff8 = -2^-17 @@ -345,6 +511,16 @@ mod tests { assert!(a.eq(&b) && b.eq(&a)); assert_eq!(a.partial_cmp(&b), Some(Equal)); assert_eq!(b.partial_cmp(&a), Some(Equal)); + assert!(a > 0.0); + assert_eq!(a, (-16f64).exp2()); + assert!(a <= (-16f64).exp2()); + assert!(a >= (-16f64).exp2()); + assert!(a > -(-16f64).exp2()); + assert_ne!(a, 0.75 * (-16f64).exp2()); + assert!(a > 0.75 * (-16f64).exp2()); + assert!(a >= 0.75 * (-16f64).exp2()); + assert!(a < 1.25 * (-16f64).exp2()); + assert!(a <= 1.25 * (-16f64).exp2()); a >>= 1; b >>= 1; // a = 0000.0000 = 0, b = 000.00008 = 2^-17 diff --git a/src/sealed_float.rs b/src/sealed_float.rs index a20d54c..c33c717 100644 --- a/src/sealed_float.rs +++ b/src/sealed_float.rs @@ -33,6 +33,7 @@ pub trait SealedFloat: Copy + Debug + Display { fn zero(neg: bool) -> Self; fn infinity(neg: bool) -> Self; fn is_nan(self) -> bool; + fn is_infinite(self) -> bool; fn is_finite(self) -> bool; fn is_zero(self) -> bool; fn is_sign_negative(self) -> bool; @@ -74,6 +75,11 @@ macro_rules! sealed_float { (self.to_bits() & !Self::SIGN_MASK) > Self::EXP_MASK } + #[inline] + fn is_infinite(self) -> bool { + (self.to_bits() & !Self::SIGN_MASK) == Self::EXP_MASK + } + #[inline] fn is_finite(self) -> bool { (self.to_bits() & !Self::SIGN_MASK) < Self::EXP_MASK