fix comparison of e.g. I0F16 with +0.5

This commit is contained in:
Trevor Spiteri 2020-04-16 21:19:30 +02:00
parent 4c25f002a1
commit 5db3b17284
5 changed files with 268 additions and 44 deletions

View File

@ -76,6 +76,9 @@ The conversions supported cover the following cases.
### Version 0.5.5 news (unreleased)
* Bug fix: comparison between a signed fixed-point number of type
`FixedI` and a number that would overflow when converting to
`FixedI` by exactly one bit was giving an incorrect result.
* The following associated constants were added to all fixed-point
types, to the [`Fixed`] trait, and to the [`Wrapping`] wrapper:
* [`MIN`], [`MAX`]

View File

@ -8,6 +8,9 @@ as-is, without any warranty. -->
Version 0.5.5 (unreleased)
==========================
* Bug fix: comparison between a signed fixed-point number of type
`FixedI` and a number that would overflow when converting to
`FixedI` by exactly one bit was giving an incorrect result.
* The following associated constants were added to all fixed-point
types, to the `Fixed` trait, and to the `Wrapping` wrapper:
* `MIN`, `MAX`

View File

@ -34,18 +34,22 @@ macro_rules! fixed_cmp_fixed {
Self::FRAC_NBITS,
Self::INT_NBITS,
);
let rhs_bits = match conv.bits {
Widest::Unsigned(bits) => bits as <Self as Fixed>::Bits,
Widest::Negative(bits) => bits as <Self as Fixed>::Bits,
let (rhs_is_neg, rhs_bits) = match conv.bits {
Widest::Unsigned(bits) => (false, bits as <Self as Fixed>::Bits),
Widest::Negative(bits) => (true, bits as <Self as Fixed>::Bits),
};
conv.dir == Ordering::Equal && !conv.overflow && rhs_bits == self.to_bits()
conv.dir == Ordering::Equal
&& !conv.overflow
&& rhs_is_neg == rhs_bits.is_negative()
&& rhs_bits == self.to_bits()
}
}
impl<FracLhs: $LhsLeEqU, FracRhs: $RhsLeEqU> PartialOrd<$Rhs<FracRhs>> for $Lhs<FracLhs> {
#[inline]
fn partial_cmp(&self, rhs: &$Rhs<FracRhs>) -> Option<Ordering> {
match (self.to_bits().is_negative(), rhs.to_bits().is_negative()) {
let rhs_is_neg = rhs.to_bits().is_negative();
match (self.to_bits().is_negative(), rhs_is_neg) {
(false, true) => return Some(Ordering::Greater),
(true, false) => return Some(Ordering::Less),
_ => {}
@ -55,23 +59,24 @@ macro_rules! fixed_cmp_fixed {
Self::FRAC_NBITS,
Self::INT_NBITS,
);
if conv.overflow {
return if rhs.to_bits().is_negative() {
let rhs_bits = match conv.bits {
Widest::Unsigned(bits) => bits as <Self as Fixed>::Bits,
Widest::Negative(bits) => bits as <Self as Fixed>::Bits,
};
if conv.overflow || rhs_bits.is_negative() != rhs_is_neg {
return if rhs_is_neg {
Some(Ordering::Greater)
} else {
Some(Ordering::Less)
};
}
let rhs_bits = match conv.bits {
Widest::Unsigned(bits) => bits as <Self as Fixed>::Bits,
Widest::Negative(bits) => bits as <Self as Fixed>::Bits,
};
Some(self.to_bits().cmp(&rhs_bits).then(conv.dir))
}
#[inline]
fn lt(&self, rhs: &$Rhs<FracRhs>) -> bool {
match (self.to_bits().is_negative(), rhs.to_bits().is_negative()) {
let rhs_is_neg = rhs.to_bits().is_negative();
match (self.to_bits().is_negative(), rhs_is_neg) {
(false, true) => return false,
(true, false) => return true,
_ => {}
@ -81,15 +86,15 @@ macro_rules! fixed_cmp_fixed {
Self::FRAC_NBITS,
Self::INT_NBITS,
);
if conv.overflow {
return !rhs.to_bits().is_negative();
}
let rhs_bits = match conv.bits {
Widest::Unsigned(bits) => bits as <Self as Fixed>::Bits,
Widest::Negative(bits) => bits as <Self as Fixed>::Bits,
};
self.to_bits() < rhs_bits
|| (self.to_bits() == rhs_bits && conv.dir == Ordering::Less)
if conv.overflow || rhs_bits.is_negative() != rhs_is_neg {
return !rhs_is_neg;
}
let lhs_bits = self.to_bits();
lhs_bits < rhs_bits || (lhs_bits == rhs_bits && conv.dir == Ordering::Less)
}
#[inline]
@ -191,11 +196,14 @@ macro_rules! fixed_cmp_float {
FloatKind::Finite { conv, .. } => conv,
_ => return false,
};
let rhs_bits = match conv.bits {
Widest::Unsigned(bits) => bits as <Self as Fixed>::Bits,
Widest::Negative(bits) => bits as <Self as Fixed>::Bits,
let (rhs_is_neg, rhs_bits) = match conv.bits {
Widest::Unsigned(bits) => (false, bits as <Self as Fixed>::Bits),
Widest::Negative(bits) => (true, bits as <Self as Fixed>::Bits),
};
conv.dir == Ordering::Equal && !conv.overflow && rhs_bits == self.to_bits()
conv.dir == Ordering::Equal
&& !conv.overflow
&& rhs_is_neg == rhs_bits.is_negative()
&& rhs_bits == self.to_bits()
}
}
@ -226,17 +234,17 @@ macro_rules! fixed_cmp_float {
(true, false) => return Some(Ordering::Less),
_ => {}
}
if conv.overflow {
let rhs_bits = match conv.bits {
Widest::Unsigned(bits) => bits as <Self as Fixed>::Bits,
Widest::Negative(bits) => bits as <Self as Fixed>::Bits,
};
if conv.overflow || rhs_bits.is_negative() != rhs_is_neg {
return if rhs_is_neg {
Some(Ordering::Greater)
} else {
Some(Ordering::Less)
};
}
let rhs_bits = match conv.bits {
Widest::Unsigned(bits) => bits as <Self as Fixed>::Bits,
Widest::Negative(bits) => bits as <Self as Fixed>::Bits,
};
Some(self.to_bits().cmp(&rhs_bits).then(conv.dir))
}
@ -254,13 +262,13 @@ macro_rules! fixed_cmp_float {
(true, false) => return true,
_ => {}
}
if conv.overflow {
return !rhs_is_neg;
}
let rhs_bits = match conv.bits {
Widest::Unsigned(bits) => bits as <Self as Fixed>::Bits,
Widest::Negative(bits) => bits as <Self as Fixed>::Bits,
};
if conv.overflow || rhs_bits.is_negative() != rhs_is_neg {
return !rhs_is_neg;
}
let lhs_bits = self.to_bits();
lhs_bits < rhs_bits || (lhs_bits == rhs_bits && conv.dir == Ordering::Less)
}
@ -301,13 +309,13 @@ macro_rules! fixed_cmp_float {
(true, false) => return true,
_ => {}
}
if conv.overflow {
return lhs_is_neg;
}
let lhs_bits = match conv.bits {
Widest::Unsigned(bits) => bits as <$Fix<Frac> as Fixed>::Bits,
Widest::Negative(bits) => bits as <$Fix<Frac> as Fixed>::Bits,
};
if conv.overflow || lhs_bits.is_negative() != lhs_is_neg {
return lhs_is_neg;
}
let rhs_bits = rhs.to_bits();
lhs_bits < rhs_bits || (lhs_bits == rhs_bits && conv.dir == Ordering::Greater)
}
@ -509,4 +517,69 @@ mod tests {
assert_eq!(a, 1i32 << 12);
assert_eq!(b, 0);
}
#[test]
fn cmp_i0_with_half() {
use crate::types::*;
assert_eq!(I0F32::checked_from_num(0.5), None);
for &float in &[-0.5, -0.25, 0., 0.25, 0.49] {
let fixed = I0F32::from_num(float);
let half = U0F32::from_num(0.5);
assert_eq!(fixed < half, float < 0.5, "{} < {}", fixed, half);
assert_eq!(fixed == half, float == 0.5, "{} == {}", fixed, half);
assert_eq!(fixed > half, float > 0.5, "{} > {}", fixed, half);
assert_eq!(
fixed.partial_cmp(&half),
float.partial_cmp(&0.5),
"{}.partial_cmp(&{})",
fixed,
half
);
assert_eq!(half < fixed, fixed > half);
assert_eq!(half == fixed, fixed == half);
assert_eq!(half > fixed, fixed < half);
assert_eq!(
half.partial_cmp(&fixed),
fixed.partial_cmp(&half).map(Ordering::reverse)
);
let half = I1F31::from_num(0.5);
assert_eq!(fixed < half, float < 0.5, "{} < {}", fixed, half);
assert_eq!(fixed == half, float == 0.5, "{} == {}", fixed, half);
assert_eq!(fixed > half, float > 0.5, "{} > {}", fixed, half);
assert_eq!(
fixed.partial_cmp(&half),
float.partial_cmp(&0.5),
"{}.partial_cmp(&{})",
fixed,
half
);
assert_eq!(half < fixed, fixed > half);
assert_eq!(half == fixed, fixed == half);
assert_eq!(half > fixed, fixed < half);
assert_eq!(
half.partial_cmp(&fixed),
fixed.partial_cmp(&half).map(Ordering::reverse)
);
let half = 0.5f32;
assert_eq!(fixed < half, float < 0.5, "{} < {}", fixed, half);
assert_eq!(fixed == half, float == 0.5, "{} == {}", fixed, half);
assert_eq!(fixed > half, float > 0.5, "{} > {}", fixed, half);
assert_eq!(
fixed.partial_cmp(&half),
float.partial_cmp(&0.5),
"{}.partial_cmp(&{})",
fixed,
half
);
assert_eq!(half < fixed, fixed > half);
assert_eq!(half == fixed, fixed == half);
assert_eq!(half > fixed, fixed < half);
assert_eq!(
half.partial_cmp(&fixed),
fixed.partial_cmp(&half).map(Ordering::reverse)
);
}
}
}

View File

@ -311,13 +311,16 @@ mod macros_round;
mod macros_no_frac;
#[macro_use]
mod macros_frac;
#[macro_use]
mod macros_const;
macro_rules! fixed {
(
$description:expr,
$Fixed:ident($Inner:ty, $LeEqU:tt, $s_nbits:expr, $s_nbits_m4:expr),
$nbytes:expr, $bytes_val:expr, $be_bytes:expr, $le_bytes:expr,
$UInner:ty, $Signedness:tt
$UInner:ty, $Signedness:tt,
$LeEqU_C0:tt, $LeEqU_C1:tt, $LeEqU_C2:tt, $LeEqU_C3:tt
) => {
fixed! {
$description,
@ -332,7 +335,8 @@ macro_rules! fixed {
$Inner:ty[$s_inner:expr], $LeEqU:tt, $s_nbits:expr, $s_nbits_m4:expr
),
$nbytes:expr, $bytes_val:expr, $be_bytes:expr, $le_bytes:expr,
$UInner:ty, $Signedness:tt
$UInner:ty, $Signedness:tt,
$LeEqU_C0:tt, $LeEqU_C1:tt, $LeEqU_C2:tt, $LeEqU_C3:tt
) => {
comment! {
$description,
@ -409,6 +413,9 @@ assert_eq!(two_point_75.to_string(), \"2.8\");
$Fixed[$s_fixed]($Inner[$s_inner], $LeEqU, $s_nbits, $s_nbits_m4),
$UInner, $Signedness
}
fixed_const! {
$Fixed[$s_fixed]($LeEqU, $LeEqU_C0, $LeEqU_C1, $LeEqU_C2, $LeEqU_C3)
}
};
}
@ -416,19 +423,22 @@ fixed! {
"An eight-bit fixed-point unsigned",
FixedU8(u8, LeEqU8, "8", "4"),
1, "0x12", "[0x12]", "[0x12]",
u8, Unsigned
u8, Unsigned,
LeEqU8, LeEqU7, LeEqU6, LeEqU5
}
fixed! {
"A 16-bit fixed-point unsigned",
FixedU16(u16, LeEqU16, "16", "12"),
2, "0x1234", "[0x12, 0x34]", "[0x34, 0x12]",
u16, Unsigned
u16, Unsigned,
LeEqU16, LeEqU15, LeEqU14, LeEqU13
}
fixed! {
"A 32-bit fixed-point unsigned",
FixedU32(u32, LeEqU32, "32", "28"),
4, "0x1234_5678", "[0x12, 0x34, 0x56, 0x78]", "[0x78, 0x56, 0x34, 0x12]",
u32, Unsigned
u32, Unsigned,
LeEqU32, LeEqU31, LeEqU30, LeEqU29
}
fixed! {
"A 64-bit fixed-point unsigned",
@ -436,7 +446,8 @@ fixed! {
8, "0x1234_5678_9ABC_DEF0",
"[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]",
"[0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12]",
u64, Unsigned
u64, Unsigned,
LeEqU64, LeEqU63, LeEqU62, LeEqU61
}
fixed! {
"A 128-bit fixed-point unsigned",
@ -446,25 +457,29 @@ fixed! {
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]",
"[0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12, \
0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12]",
u128, Unsigned
u128, Unsigned,
LeEqU128, LeEqU127, LeEqU126, LeEqU125
}
fixed! {
"An eight-bit fixed-point signed",
FixedI8(i8, LeEqU8, "8", "4"),
1, "0x12", "[0x12]", "[0x12]",
u8, Signed
u8, Signed,
LeEqU7, LeEqU6, LeEqU5, LeEqU4
}
fixed! {
"A 16-bit fixed-point signed",
FixedI16(i16, LeEqU16, "16", "12"),
2, "0x1234", "[0x12, 0x34]", "[0x34, 0x12]",
u16, Signed
u16, Signed,
LeEqU15, LeEqU14, LeEqU13, LeEqU12
}
fixed! {
"A 32-bit fixed-point signed",
FixedI32(i32, LeEqU32, "32", "28"),
4, "0x1234_5678", "[0x12, 0x34, 0x56, 0x78]", "[0x78, 0x56, 0x34, 0x12]",
u32, Signed
u32, Signed,
LeEqU31, LeEqU30, LeEqU29, LeEqU28
}
fixed! {
"A 64-bit fixed-point signed",
@ -472,7 +487,8 @@ fixed! {
8, "0x1234_5678_9ABC_DEF0",
"[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]",
"[0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12]",
u64, Signed
u64, Signed,
LeEqU63, LeEqU62, LeEqU61, LeEqU60
}
fixed! {
"A 128-bit fixed-point signed",
@ -482,7 +498,8 @@ fixed! {
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]",
"[0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12, \
0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12]",
u128, Signed
u128, Signed,
LeEqU127, LeEqU126, LeEqU125, LeEqU124
}
#[cfg(test)]

128
src/macros_const.rs Normal file
View File

@ -0,0 +1,128 @@
// Copyright © 20182020 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
// split shift in two parts in case it is equal to 128
macro_rules! split {
($CONST:ident >> (128 - $frac:expr)) => {
consts::$CONST >> (64 - $frac / 2) >> (64 + $frac / 2 - $frac)
};
}
macro_rules! fixed_const {
(
$Fixed:ident[$s_fixed:expr](
$LeEqU:tt, $LeEqU_C0:tt, $LeEqU_C1:tt, $LeEqU_C2:tt, $LeEqU_C3:tt
)
) => {
// 0 ≤ constant < 0.5
impl<Frac: $LeEqU> $Fixed<Frac> {
/// 1/τ = 0.159154…
pub const FRAC_1_TAU: U0F128 = split!(consts::FRAC_1_TAU >> (128 - Frac::U32));
/// 2/τ = 0.318309…
pub const FRAC_2_TAU: U0F128 = split!(consts::FRAC_2_TAU >> (128 - Frac::U32));
/// π/8 = 0.392699…
pub const FRAC_PI_8: U0F128 = split!(consts::FRAC_PI_8 >> (128 - Frac::U32));
/// 1/π = 0.318309…
pub const FRAC_1_PI: U0F128 = split!(consts::FRAC_1_PI >> (128 - Frac::U32));
/// log<sub>10</sub> 2 = 0.301029…
pub const LOG10_2: U0F128 = split!(consts::LOG10_2 >> (128 - Frac::U32));
/// log<sub>10</sub> e = 0.434294…
pub const LOG10_E: U0F128 = split!(consts::LOG10_E >> (128 - Frac::U32));
}
// 0.5 ≤ constant < 1
impl<Frac: $LeEqU_C0> $Fixed<Frac> {
/// τ/8 = 0.785398…
pub const FRAC_TAU_8: U0F128 = split!(consts::FRAC_TAU_8 >> (128 - Frac::U32));
/// τ/12 = 0.523598…
pub const FRAC_TAU_12: U0F128 = split!(consts::FRAC_TAU_12 >> (128 - Frac::U32));
/// 4/τ = 0.636619…
pub const FRAC_4_TAU: U0F128 = split!(consts::FRAC_4_TAU >> (128 - Frac::U32));
/// π/4 = 0.785398…
pub const FRAC_PI_4: U0F128 = split!(consts::FRAC_PI_4 >> (128 - Frac::U32));
/// π/6 = 0.523598…
pub const FRAC_PI_6: U0F128 = split!(consts::FRAC_PI_6 >> (128 - Frac::U32));
/// 2/π = 0.636619…
pub const FRAC_2_PI: U0F128 = split!(consts::FRAC_2_PI >> (128 - Frac::U32));
/// 1/√2 = 0.707106…
pub const FRAC_1_SQRT_2: U0F128 = split!(consts::FRAC_1_SQRT_2 >> (128 - Frac::U32));
/// ln 2 = 0.693147…
pub const LN_2: U0F128 = split!(consts::LN_2 >> (128 - Frac::U32));
}
// 1 ≤ constant < 2
impl<Frac: $LeEqU_C1> $Fixed<Frac> {
/// τ/4 = 1.57079…
pub const FRAC_TAU_4: U1F127 = consts::FRAC_TAU_4 >> (127 - Frac::U32);
/// τ/6 = 1.04719…
pub const FRAC_TAU_6: U1F127 = consts::FRAC_TAU_6 >> (127 - Frac::U32);
/// π/2 = 1.57079…
pub const FRAC_PI_2: U1F127 = consts::FRAC_PI_2 >> (127 - Frac::U32);
/// π/3 = 1.04719…
pub const FRAC_PI_3: U1F127 = consts::FRAC_PI_3 >> (127 - Frac::U32);
/// 2/√π = 1.12837…
pub const FRAC_2_SQRT_PI: U1F127 = consts::FRAC_2_SQRT_PI >> (127 - Frac::U32);
/// √2 = 1.41421…
pub const SQRT_2: U1F127 = consts::SQRT_2 >> (127 - Frac::U32);
/// log<sub>2</sub> e = 1.44269…
pub const LOG2_E: U1F127 = consts::LOG2_E >> (127 - Frac::U32);
}
// 2 ≤ constant < 4
impl<Frac: $LeEqU_C2> $Fixed<Frac> {
/// τ/2 = 3.14159…
pub const FRAC_TAU_2: U2F126 = consts::FRAC_TAU_2 >> (126 - Frac::U32);
/// τ/3 = 2.09439…
pub const FRAC_TAU_3: U2F126 = consts::FRAC_TAU_3 >> (126 - Frac::U32);
/// π = 3.14159…
pub const PI: U2F126 = consts::PI >> (126 - Frac::U32);
/// e = 2.71828…
pub const E: U2F126 = consts::E >> (126 - Frac::U32);
/// log<sub>2</sub> 10 = 3.32192…
pub const LOG2_10: U2F126 = consts::LOG2_10 >> (126 - Frac::U32);
/// ln 10 = 2.30258…
pub const LN_10: U2F126 = consts::LN_10 >> (126 - Frac::U32);
}
// 4 ≤ constant < 8
impl<Frac: $LeEqU_C3> $Fixed<Frac> {
/// τ = 6.28318…
pub const TAU: U3F125 = consts::TAU >> (125 - Frac::U32);
}
};
}