implement comparisons between fixed and float
This commit is contained in:
parent
8c0117877b
commit
27c9ee7b0f
18
README.md
18
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.
|
||||
* <mark>Every fixed-point type now supports coversion to/from all
|
||||
primitive number types, including checked versions of the
|
||||
conversions.</mark>
|
||||
* <mark>Every fixed-point type now supports comparisons with all
|
||||
primitive number types.</mark>
|
||||
* 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.
|
||||
|
|
13
RELEASES.md
13
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.
|
||||
|
|
230
src/cmp.rs
230
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<FracRhs>) -> bool {
|
||||
!self.gt(rhs)
|
||||
!rhs.lt(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn gt(&self, rhs: &$Rhs<FracRhs>) -> 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<FracRhs>>::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 <Self as SealedFixed>::Bits,
|
||||
Widest::Negative(bits) => bits as <Self as SealedFixed>::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<Frac>) -> bool {
|
||||
self.to_repr_fixed().le(rhs)
|
||||
!rhs.lt(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn gt(&self, rhs: &$Fix<Frac>) -> bool {
|
||||
self.to_repr_fixed().gt(rhs)
|
||||
rhs.lt(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ge(&self, rhs: &$Fix<Frac>) -> bool {
|
||||
self.to_repr_fixed().ge(rhs)
|
||||
!self.lt(rhs)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! fixed_cmp_float {
|
||||
($Fix:ident($NBits:ident), $Float:ident) => {
|
||||
impl<Frac> PartialEq<$Float> for $Fix<Frac>
|
||||
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 <Self as SealedFixed>::Bits,
|
||||
Widest::Negative(bits) => bits as <Self as SealedFixed>::Bits,
|
||||
};
|
||||
dir == Ordering::Equal && !overflow && rhs_bits == self.to_bits()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Frac> PartialEq<$Fix<Frac>> for $Float
|
||||
where
|
||||
Frac: Unsigned + IsLessOrEqual<$NBits, Output = True>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &$Fix<Frac>) -> bool {
|
||||
rhs.eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Frac> PartialOrd<$Float> for $Fix<Frac>
|
||||
where
|
||||
Frac: Unsigned + IsLessOrEqual<$NBits, Output = True>,
|
||||
{
|
||||
#[inline]
|
||||
fn partial_cmp(&self, rhs: &$Float) -> Option<Ordering> {
|
||||
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 <Self as SealedFixed>::Bits,
|
||||
Widest::Negative(bits) => bits as <Self as SealedFixed>::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 <Self as SealedFixed>::Bits,
|
||||
Widest::Negative(bits) => bits as <Self as SealedFixed>::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<Frac> PartialOrd<$Fix<Frac>> for $Float
|
||||
where
|
||||
Frac: Unsigned + IsLessOrEqual<$NBits, Output = True>,
|
||||
{
|
||||
#[inline]
|
||||
fn partial_cmp(&self, rhs: &$Fix<Frac>) -> Option<Ordering> {
|
||||
rhs.partial_cmp(self).map(Ordering::reverse)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn lt(&self, rhs: &$Fix<Frac>) -> 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>>::FRAC_NBITS, <$Fix<Frac>>::INT_NBITS);
|
||||
if overflow {
|
||||
return lhs_is_neg;
|
||||
}
|
||||
let lhs_bits = match lhs_128 {
|
||||
Widest::Unsigned(bits) => bits as <$Fix<Frac> as SealedFixed>::Bits,
|
||||
Widest::Negative(bits) => bits as <$Fix<Frac> 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<Frac>) -> bool {
|
||||
!SealedFloat::is_nan(*self) && !rhs.lt(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn gt(&self, rhs: &$Fix<Frac>) -> bool {
|
||||
rhs.lt(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ge(&self, rhs: &$Fix<Frac>) -> 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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue