implement reciprocal methods

This commit is contained in:
Trevor Spiteri 2020-10-20 21:18:11 +02:00
parent 20e5243c45
commit b09acfdf56
2 changed files with 280 additions and 15 deletions

View File

@ -382,7 +382,7 @@ macro_rules! fixed {
$s_nbits_m1:expr, $s_nbits_m2:expr, $s_nbits_m3:expr, $s_nbits_m4:expr
),
$nbytes:expr, $bytes_val:expr, $be_bytes:expr, $le_bytes:expr,
$UInner:ty, $Signedness:tt,
$UFixed:ident, $UInner:ty, $Signedness:tt,
$LeEqU_C0:tt, $LeEqU_C1:tt, $LeEqU_C2:tt, $LeEqU_C3:tt
) => {
fixed! {
@ -392,7 +392,7 @@ macro_rules! fixed {
$s_nbits_m1, $s_nbits_m2, $s_nbits_m3, $s_nbits_m4
),
$nbytes, $bytes_val, $be_bytes, $le_bytes,
$UInner, $Signedness,
$UFixed, $UInner, $Signedness,
$LeEqU_C0, $LeEqU_C1, $LeEqU_C2, $LeEqU_C3
}
};
@ -403,7 +403,7 @@ macro_rules! fixed {
$s_nbits_m1:expr, $s_nbits_m2:expr, $s_nbits_m3:expr, $s_nbits_m4:expr
),
$nbytes:expr, $bytes_val:expr, $be_bytes:expr, $le_bytes:expr,
$UInner:ty, $Signedness:tt,
$UFixed:ident, $UInner:ty, $Signedness:tt,
$LeEqU_C0:tt, $LeEqU_C1:tt, $LeEqU_C2:tt, $LeEqU_C3:tt
) => {
comment! {
@ -477,7 +477,7 @@ assert_eq!(two_point_75.to_string(), \"2.8\");
// inherent methods that require Frac bounds, and cannot be const
fixed_frac! {
$Fixed[$s_fixed]($Inner[$s_inner], $LeEqU, $s_nbits, $s_nbits_m1, $s_nbits_m4),
$UInner, $Signedness
$UFixed, $UInner, $Signedness
}
fixed_const! {
$Fixed[$s_fixed]($LeEqU, $s_nbits, $s_nbits_m1, $s_nbits_m2, $s_nbits_m3, $s_nbits_m4),
@ -491,21 +491,21 @@ fixed! {
"An eight-bit fixed-point unsigned",
FixedU8(u8, LeEqU8, "8", "7", "6", "5", "4"),
1, "0x12", "[0x12]", "[0x12]",
u8, Unsigned,
FixedU8, u8, Unsigned,
U8, U7, U6, U5
}
fixed! {
"A 16-bit fixed-point unsigned",
FixedU16(u16, LeEqU16, "16", "15", "14", "13", "12"),
2, "0x1234", "[0x12, 0x34]", "[0x34, 0x12]",
u16, Unsigned,
FixedU16, u16, Unsigned,
U16, U15, U14, U13
}
fixed! {
"A 32-bit fixed-point unsigned",
FixedU32(u32, LeEqU32, "32", "31", "30", "29", "28"),
4, "0x1234_5678", "[0x12, 0x34, 0x56, 0x78]", "[0x78, 0x56, 0x34, 0x12]",
u32, Unsigned,
FixedU32, u32, Unsigned,
U32, U31, U30, U29
}
fixed! {
@ -514,7 +514,7 @@ fixed! {
8, "0x1234_5678_9ABC_DEF0",
"[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]",
"[0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12]",
u64, Unsigned,
FixedU64, u64, Unsigned,
U64, U63, U62, U61
}
fixed! {
@ -525,28 +525,28 @@ 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,
FixedU128, u128, Unsigned,
U128, U127, U126, U125
}
fixed! {
"An eight-bit fixed-point signed",
FixedI8(i8, LeEqU8, "8", "7", "6", "5", "4"),
1, "0x12", "[0x12]", "[0x12]",
u8, Signed,
FixedU8, u8, Signed,
U7, U6, U5, U4
}
fixed! {
"A 16-bit fixed-point signed",
FixedI16(i16, LeEqU16, "16", "15", "14", "13", "12"),
2, "0x1234", "[0x12, 0x34]", "[0x34, 0x12]",
u16, Signed,
FixedU16, u16, Signed,
U15, U14, U13, U12
}
fixed! {
"A 32-bit fixed-point signed",
FixedI32(i32, LeEqU32, "32", "31", "30", "29", "28"),
4, "0x1234_5678", "[0x12, 0x34, 0x56, 0x78]", "[0x78, 0x56, 0x34, 0x12]",
u32, Signed,
FixedU32, u32, Signed,
U31, U30, U29, U28
}
fixed! {
@ -555,7 +555,7 @@ fixed! {
8, "0x1234_5678_9ABC_DEF0",
"[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]",
"[0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12]",
u64, Signed,
FixedU64, u64, Signed,
U63, U62, U61, U60
}
fixed! {
@ -566,7 +566,7 @@ 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,
FixedU128, u128, Signed,
U127, U126, U125, U124
}
@ -1316,4 +1316,62 @@ mod tests {
(U16F16::from_num(4), false)
);
}
#[test]
fn reciprocals() {
// 4/3 wraps to 1/3 = 0x0.5555_5555
assert_eq!(
U0F32::from_num(0.75).overflowing_recip(),
(U0F32::from_bits(0x5555_5555), true)
);
// 8/3 wraps to 2/3 = 0x0.AAAA_AAAA
assert_eq!(
U0F32::from_num(0.375).overflowing_recip(),
(U0F32::from_bits(0xAAAA_AAAA), true)
);
// 8/3 wraps to 2/3 = 0x0.AAAA_AAAA, which is -0x0.5555_5556
assert_eq!(
I0F32::from_num(0.375).overflowing_recip(),
(I0F32::from_bits(-0x5555_5556), true)
);
assert_eq!(
I0F32::from_num(-0.375).overflowing_recip(),
(I0F32::from_bits(0x5555_5556), true)
);
// -2 wraps to 0
assert_eq!(
I0F32::from_num(-0.5).overflowing_recip(),
(I0F32::from_num(0), true)
);
// 8/3 wraps to 2/3 = 0x0.AAAA_AAAA (bits 0x5555_5555)
assert_eq!(
I1F31::from_num(0.375).overflowing_recip(),
(I1F31::from_bits(0x5555_5555), true)
);
assert_eq!(
I1F31::from_num(-0.375).overflowing_recip(),
(I1F31::from_bits(-0x5555_5555), true)
);
// 4/3 = 0x1.5555_5554 (bits 0xAAAA_AAAA, or -0x5555_5556)
assert_eq!(
I1F31::from_num(0.75).overflowing_recip(),
(I1F31::from_bits(-0x5555_5556), true)
);
assert_eq!(
I1F31::from_num(-0.75).overflowing_recip(),
(I1F31::from_bits(0x5555_5556), true)
);
// -2 wraps to 0
assert_eq!(
I1F31::from_num(-0.5).overflowing_recip(),
(I1F31::from_num(0), true)
);
// -1 does not overflow
assert_eq!(
I1F31::from_num(-1).overflowing_recip(),
(I1F31::from_num(-1), false)
);
}
}

View File

@ -18,7 +18,7 @@ macro_rules! fixed_frac {
$Fixed:ident[$s_fixed:expr](
$Inner:ty[$s_inner:expr], $LeEqU:tt, $s_nbits:expr, $s_nbits_m1:expr, $s_nbits_m4:expr
),
$UInner:ty, $Signedness:tt
$UFixed:ident, $UInner:ty, $Signedness:tt
) => {
/// The implementation of items in this block depends on the
/// number of fractional bits `Frac`.
@ -223,6 +223,35 @@ assert_eq!(Fix::from_num(-5).signum(), -1);
}
}
comment! {
"Returns the reciprocal (inverse) of the fixed-point number, 1/`self`.
# Panics
Panics if the fixed-point number is zero.
When debug assertions are enabled, this method also panics if the
reciprocal overflows. When debug assertions are not enabled, the
wrapped value can be returned, but it is not considered a breaking
change if in the future it panics; if wrapping is required use
[`wrapping_recip`] instead.
# Examples
```rust
use fixed::{types::extra::U4, ", $s_fixed, "};
type Fix = ", $s_fixed, "<U4>;
assert_eq!(Fix::from_num(2).recip(), Fix::from_num(0.5));
```
[`wrapping_recip`]: #method.wrapping_recip
";
#[inline] pub fn recip(self) -> $Fixed<Frac> {
let (ans, overflow) = self.overflowing_recip();
debug_assert!(!overflow, "overflow"); ans
}
}
comment! {
"Euclidean division.
@ -443,6 +472,34 @@ assert_eq!(Fix::MAX.checked_div(Fix::from_num(1) / 2), None);
}
}
comment! {
"Checked reciprocal. Returns the reciprocal, or
[`None`] if `self` is zero or on overflow.
# Examples
```rust
use fixed::{types::extra::U4, ", $s_fixed, "};
type Fix = ", $s_fixed, "<U4>;
assert_eq!(Fix::from_num(2).checked_recip(), Some(Fix::from_num(0.5)));
assert_eq!(Fix::from_num(0).checked_recip(), None);
```
[`None`]: https://doc.rust-lang.org/nightly/core/option/enum.Option.html#variant.None
";
#[inline]
pub fn checked_recip(self) -> Option<$Fixed<Frac>> {
if self.to_bits() == 0 {
None
} else {
match self.overflowing_recip() {
(ans, false) => Some(ans),
(_, true) => None,
}
}
}
}
comment! {
"Checked Euclidean division. Returns the quotient, or
[`None`] if the divisor is zero or on overflow.
@ -730,6 +787,44 @@ assert_eq!(Fix::MAX.saturating_div(one_half), Fix::MAX);
}
}
comment! {
"Saturating reciprocal. Returns the reciprocal,
saturating on overflow.
# Panics
Panics if the fixed-point number is zero.
# Examples
```rust
use fixed::{types::extra::U", $s_nbits_m1, ", ", $s_fixed, "};
// only one integer bit
type Fix = ", $s_fixed, "<U", $s_nbits_m1, ">;
assert_eq!(Fix::from_num(0.25).saturating_recip(), Fix::MAX);
",
if_signed_else_empty_str! {
$Signedness,
"assert_eq!(Fix::from_num(-0.25).saturating_recip(), Fix::MIN);
",
},
"```
";
#[inline]
pub fn saturating_recip(self) -> $Fixed<Frac> {
match self.overflowing_recip() {
(ans, false) => ans,
(_, true) => {
if self < 0 {
Self::MIN
} else {
Self::MAX
}
}
}
}
}
comment! {
"Saturating Euclidean division. Returns the quotient,
saturating on overflow.
@ -859,6 +954,30 @@ assert_eq!(Fix::MAX.wrapping_div(quarter), wrapped);
}
}
comment! {
"Wrapping reciprocal. Returns the reciprocal,
wrapping on overflow.
# Panics
Panics if the fixed-point number is zero.
# Examples
```rust
use fixed::{types::extra::U", $s_nbits_m1, ", ", $s_fixed, "};
// only one integer bit
type Fix = ", $s_fixed, "<U", $s_nbits_m1, ">;
assert_eq!(Fix::from_num(0.25).wrapping_recip(), Fix::from_num(0));
```
";
#[inline]
pub fn wrapping_recip(self) -> $Fixed<Frac> {
let (ans, _) = self.overflowing_recip();
ans
}
}
comment! {
"Wrapping Euclidean division. Returns the quotient, wrapping on overflow.
@ -1083,6 +1202,32 @@ let _overflow = Fix::MAX.unwrapped_div(quarter);
}
}
#[cfg(feature = "unwrapped")]
comment! {
"Unwrapped reciprocal. Returns the reciprocal,
panicking on overflow.
# Panics
Panics if the fixed-point number is zero or on overflow.
# Examples
```rust
use fixed::{types::extra::U4, ", $s_fixed, "};
type Fix = ", $s_fixed, "<U4>;
assert_eq!(Fix::from_num(0.25).unwrapped_recip(), Fix::from_num(4));
```
";
#[inline]
pub fn unwrapped_recip(self) -> $Fixed<Frac> {
match self.overflowing_recip() {
(_, true) => panic!("overflow"),
(ans, false) => ans,
}
}
}
#[cfg(feature = "unwrapped")]
comment! {
"Unwrapped Euclidean division. Returns the quotient, panicking on overflow.
@ -1343,6 +1488,68 @@ assert_eq!(Fix::MAX.overflowing_div(quarter), (wrapped, true));
}
}
comment! {
"Overflowing reciprocal.
Returns a [tuple] of the reciprocal and a [`bool`] indicating whether
an overflow has occurred. On overflow, the wrapped value is returned.
# Panics
Panics if the fixed-point number is zero.
# Examples
```rust
use fixed::{
types::extra::{U4, U", $s_nbits_m1, "},
", $s_fixed, ",
};
type Fix = ", $s_fixed, "<U4>;
// only one integer bit
type Small = ", $s_fixed, "<U", $s_nbits_m1, ">;
assert_eq!(Fix::from_num(0.25).overflowing_recip(), (Fix::from_num(4), false));
assert_eq!(Small::from_num(0.25).overflowing_recip(), (Small::from_num(0), true));
```
[`bool`]: https://doc.rust-lang.org/nightly/std/primitive.bool.html
[tuple]: https://doc.rust-lang.org/nightly/std/primitive.tuple.html
";
#[inline]
pub fn overflowing_recip(self) -> ($Fixed<Frac>, bool) {
if let Some(one) = Self::checked_from_num(1) {
return one.overflowing_div(self);
}
if_signed! {
$Signedness;
use crate::int_helper::IntHelper;
let (neg, abs) = self.to_bits().neg_abs();
let uns_abs = $UFixed::<Frac>::from_bits(abs);
let (uns_wrapped, overflow1) = uns_abs.overflowing_recip();
let (wrapped, overflow2) =
$Fixed::<Frac>::overflowing_from_num(uns_wrapped);
if wrapped == $Fixed::<Frac>::MIN && neg {
return (wrapped, overflow1);
}
if neg {
// if we do not have overflow yet, we will not overflow now
(wrapped.wrapping_neg(), overflow1 | overflow2)
} else {
(wrapped, overflow1 | overflow2)
}
}
if_unsigned! {
$Signedness;
// 0 < x < 1: 1/x = 1 + (1 - x) / x, wrapped to (1 - x) / x
// x.wrapping_neg() = 1 - x
// x = 0: we still get division by zero
(self.wrapping_neg().wrapping_div(self), true)
}
}
}
comment! {
"Overflowing Euclidean division.