add overflowing arithmetic ops

This commit is contained in:
Trevor Spiteri 2018-08-10 14:23:16 +02:00
parent ab78ff4076
commit 35ca259c4e
1 changed files with 349 additions and 46 deletions

View File

@ -75,6 +75,7 @@ additional terms or conditions.
mod display; mod display;
mod traits; mod traits;
use std::cmp::Ordering;
use std::f32; use std::f32;
use std::f64; use std::f64;
use std::iter::{Product, Sum}; use std::iter::{Product, Sum};
@ -94,6 +95,13 @@ macro_rules! if_signed {
(Unsigned => $($rem:tt)+) => { (Unsigned => $($rem:tt)+) => {
}; };
} }
macro_rules! if_unsigned {
(Signed => $($rem:tt)+) => {
};
(Unsigned => $($rem:tt)+) => {
$($rem)+
};
}
macro_rules! refs { macro_rules! refs {
(impl $Imp:ident for $Fixed:ident($Inner:ty) { $method:ident }) => { (impl $Imp:ident for $Fixed:ident($Inner:ty) { $method:ident }) => {
@ -181,6 +189,40 @@ macro_rules! pass_one {
}; };
} }
macro_rules! pass_method {
($comment:expr, $Fixed:ident($Inner:ty) => fn $method:ident()) => {
#[doc = $comment]
#[inline]
pub fn $method() -> $Fixed {
$Fixed(<$Inner>::$method())
}
};
($comment:expr, $Fixed:ident($Inner:ty) => fn $method:ident(self)) => {
#[doc = $comment]
#[inline]
pub fn $method(self) -> $Fixed {
$Fixed(<$Inner>::$method(self.0))
}
};
($comment:expr, $Fixed:ident($Inner:ty) => fn $method:ident(self) -> $ret_ty:ty) => {
#[doc = $comment]
#[inline]
pub fn $method(self) -> $ret_ty {
<$Inner>::$method(self.0)
}
};
(
$comment:expr,
$Fixed:ident($Inner:ty) => fn $method:ident(self, $param:ident: $param_ty:ty)
) => {
#[doc = $comment]
#[inline]
pub fn $method(self, $param: $param_ty) -> $Fixed {
$Fixed(<$Inner>::$method(self.0, $param))
}
};
}
macro_rules! shift { macro_rules! shift {
(impl $Imp:ident < $Rhs:ty > for $Fixed:ident($Inner:ty) { $method:ident }) => { (impl $Imp:ident < $Rhs:ty > for $Fixed:ident($Inner:ty) { $method:ident }) => {
impl $Imp<$Rhs> for $Fixed { impl $Imp<$Rhs> for $Fixed {
@ -345,6 +387,216 @@ macro_rules! fixed {
} }
impl $Fixed { impl $Fixed {
pass_method! {
"Returns the smallest value that can be represented.",
$Fixed($Inner) => fn min_value()
}
pass_method! {
"Returns the largest value that can be represented.",
$Fixed($Inner) => fn max_value()
}
pass_method! {
"Returns the number of ones in the binary representation.",
$Fixed($Inner) => fn count_ones(self) -> u32
}
pass_method! {
"Returns the number of zeros in the binary representation.",
$Fixed($Inner) => fn count_zeros(self) -> u32
}
pass_method! {
"Returns the number of leading zeros in the binary representation.",
$Fixed($Inner) => fn leading_zeros(self) -> u32
}
pass_method! {
"Returns the number of trailing zeros in the binary representation.",
$Fixed($Inner) => fn trailing_zeros(self) -> u32
}
pass_method! {
"Shifts to the left by `n` bits, wrapping the truncated bits to the right end.",
$Fixed($Inner) => fn rotate_left(self, n: u32)
}
pass_method! {
"Shifts to the right by `n` bits, wrapping the truncated bits to the left end.",
$Fixed($Inner) => fn rotate_right(self, n: u32)
}
/// Checked negation.
#[inline]
pub fn checked_neg(self) -> Option<$Fixed> {
<$Inner>::checked_neg(self.0).map($Fixed::from_bits)
}
/// Checked fixed-point addition.
#[inline]
pub fn checked_add(self, rhs: $Fixed) -> Option<$Fixed> {
<$Inner>::checked_add(self.0, rhs.0).map($Fixed::from_bits)
}
/// Checked fixed-point subtraction.
#[inline]
pub fn checked_sub(self, rhs: $Fixed) -> Option<$Fixed> {
<$Inner>::checked_sub(self.0, rhs.0).map($Fixed::from_bits)
}
/// Checked fixed-point multiplication.
#[inline]
pub fn checked_mul(self, rhs: $Fixed) -> Option<$Fixed> {
let (ans, dir) = self.0.mul_dir(rhs.0);
match dir {
Ordering::Equal => Some($Fixed(ans)),
_ => None,
}
}
/// Checked fixed-point division.
#[inline]
pub fn checked_div(self, rhs: $Fixed) -> Option<$Fixed> {
let (ans, dir) = self.0.div_dir(rhs.0);
match dir {
Ordering::Equal => Some($Fixed(ans)),
_ => None,
}
}
/// Checked fixed-point left shift.
#[inline]
pub fn checked_shl(self, rhs: u32) -> Option<$Fixed> {
<$Inner>::checked_shl(self.0, rhs).map($Fixed::from_bits)
}
/// Checked fixed-point right shift.
#[inline]
pub fn checked_shr(self, rhs: u32) -> Option<$Fixed> {
<$Inner>::checked_shr(self.0, rhs).map($Fixed::from_bits)
}
/// Saturating fixed-point addition.
#[inline]
pub fn saturating_add(self, rhs: $Fixed) -> $Fixed {
$Fixed(<$Inner>::saturating_add(self.0, rhs.0))
}
/// Saturating fixed-point subtraction.
#[inline]
pub fn saturating_sub(self, rhs: $Fixed) -> $Fixed {
$Fixed(<$Inner>::saturating_sub(self.0, rhs.0))
}
/// Saturating fixed-point multiplication.
#[inline]
pub fn saturating_mul(self, rhs: $Fixed) -> $Fixed {
let (ans, dir) = self.0.mul_dir(rhs.0);
match dir {
Ordering::Equal => $Fixed(ans),
Ordering::Less => $Fixed::max_value(),
Ordering::Greater => $Fixed::min_value(),
}
}
/// Saturating fixed-point division.
#[inline]
pub fn saturating_div(self, rhs: $Fixed) -> $Fixed {
let (ans, dir) = self.0.div_dir(rhs.0);
match dir {
Ordering::Equal => $Fixed(ans),
Ordering::Less => $Fixed::max_value(),
Ordering::Greater => $Fixed::min_value(),
}
}
/// Wrapping negation.
#[inline]
pub fn wrapping_neg(self) -> $Fixed {
$Fixed(<$Inner>::wrapping_neg(self.0))
}
/// Wrapping fixed-point addition.
#[inline]
pub fn wrapping_add(self, rhs: $Fixed) -> $Fixed {
$Fixed(<$Inner>::wrapping_add(self.0, rhs.0))
}
/// Wrapping fixed-point subtraction.
#[inline]
pub fn wrapping_sub(self, rhs: $Fixed) -> $Fixed {
$Fixed(<$Inner>::wrapping_sub(self.0, rhs.0))
}
/// Wrapping fixed-point multiplication.
#[inline]
pub fn wrapping_mul(self, rhs: $Fixed) -> $Fixed {
let (ans, _dir) = self.0.mul_dir(rhs.0);
$Fixed(ans)
}
/// Wrapping fixed-point division.
#[inline]
pub fn wrapping_div(self, rhs: $Fixed) -> $Fixed {
let (ans, _dir) = self.0.div_dir(rhs.0);
$Fixed(ans)
}
/// Wrapping fixed-point left shift.
#[inline]
pub fn wrapping_shl(self, rhs: u32) -> $Fixed {
$Fixed(<$Inner>::wrapping_shl(self.0, rhs))
}
/// Wrapping fixed-point right shift.
#[inline]
pub fn wrapping_shr(self, rhs: u32) -> $Fixed {
$Fixed(<$Inner>::wrapping_shr(self.0, rhs))
}
/// Overflowing negation.
#[inline]
pub fn overflowing_neg(self) -> ($Fixed, bool) {
let (ans, o) = <$Inner>::overflowing_neg(self.0);
($Fixed(ans), o)
}
/// Overflowing fixed-point addition.
#[inline]
pub fn overflowing_add(self, rhs: $Fixed) -> ($Fixed, bool) {
let (ans, o) = <$Inner>::overflowing_add(self.0, rhs.0);
($Fixed(ans), o)
}
/// Overflowing fixed-point subtraction.
#[inline]
pub fn overflowing_sub(self, rhs: $Fixed) -> ($Fixed, bool) {
let (ans, o) = <$Inner>::overflowing_sub(self.0, rhs.0);
($Fixed(ans), o)
}
/// Overflowing fixed-point multiplication.
#[inline]
pub fn overflowing_mul(self, rhs: $Fixed) -> ($Fixed, bool) {
let (ans, dir) = self.0.mul_dir(rhs.0);
($Fixed(ans), dir != Ordering::Equal)
}
/// Overflowing fixed-point division.
#[inline]
pub fn overflowing_div(self, rhs: $Fixed) -> ($Fixed, bool) {
let (ans, dir) = self.0.div_dir(rhs.0);
($Fixed(ans), dir != Ordering::Equal)
}
/// Overflowing fixed-point left shift.
#[inline]
pub fn overflowing_shl(self, rhs: u32) -> ($Fixed, bool) {
let (ans, o) = <$Inner>::overflowing_shl(self.0, rhs);
($Fixed(ans), o)
}
/// Overflowing fixed-point right shift.
#[inline]
pub fn overflowing_shr(self, rhs: u32) -> ($Fixed, bool) {
let (ans, o) = <$Inner>::overflowing_shr(self.0, rhs);
($Fixed(ans), o)
}
doc_comment! { doc_comment! {
concat!( concat!(
"Creates a fixed-point number of type `", "Creates a fixed-point number of type `",
@ -390,7 +642,9 @@ macro_rules! fixed {
type Output = $Fixed; type Output = $Fixed;
#[inline] #[inline]
fn mul(self, rhs: $Fixed) -> $Fixed { fn mul(self, rhs: $Fixed) -> $Fixed {
$Fixed(<$Inner as MulDiv>::mul(self.0, rhs.0)) let (ans, dir) = self.0.mul_dir(rhs.0);
debug_assert!(dir == Ordering::Equal, "overflow");
$Fixed(ans)
} }
} }
@ -399,7 +653,7 @@ macro_rules! fixed {
impl MulAssign<$Fixed> for $Fixed { impl MulAssign<$Fixed> for $Fixed {
#[inline] #[inline]
fn mul_assign(&mut self, rhs: $Fixed) { fn mul_assign(&mut self, rhs: $Fixed) {
self.0 = <$Inner as MulDiv>::mul(self.0, rhs.0) *self = <$Fixed as Mul>::mul(*self, rhs)
} }
} }
@ -409,7 +663,9 @@ macro_rules! fixed {
type Output = $Fixed; type Output = $Fixed;
#[inline] #[inline]
fn div(self, rhs: $Fixed) -> $Fixed { fn div(self, rhs: $Fixed) -> $Fixed {
$Fixed(<$Inner as MulDiv>::div(self.0, rhs.0)) let (ans, dir) = self.0.div_dir(rhs.0);
debug_assert!(dir == Ordering::Equal, "overflow");
$Fixed(ans)
} }
} }
@ -418,7 +674,7 @@ macro_rules! fixed {
impl DivAssign<$Fixed> for $Fixed { impl DivAssign<$Fixed> for $Fixed {
#[inline] #[inline]
fn div_assign(&mut self, rhs: $Fixed) { fn div_assign(&mut self, rhs: $Fixed) {
self.0 = <$Inner as MulDiv>::div(self.0, rhs.0) *self = <$Fixed as Div>::div(*self, rhs)
} }
} }
@ -479,7 +735,6 @@ macro_rules! fixed {
}; };
} }
fixed! { "An eight-bit fixed-point unsigned integer", FixedU8(u8), Unsigned } fixed! { "An eight-bit fixed-point unsigned integer", FixedU8(u8), Unsigned }
fixed! { "A 16-bit fixed-point unsigned integer", FixedU16(u16), Unsigned } fixed! { "A 16-bit fixed-point unsigned integer", FixedU16(u16), Unsigned }
fixed! { "A 32-bit fixed-point unsigned integer", FixedU32(u32), Unsigned } fixed! { "A 32-bit fixed-point unsigned integer", FixedU32(u32), Unsigned }
@ -491,32 +746,49 @@ fixed! { "A 32-bit fixed-point signed integer", FixedI32(i32), Signed }
fixed! { "A 64-bit fixed-point signed integer", FixedI64(i64), Signed } fixed! { "A 64-bit fixed-point signed integer", FixedI64(i64), Signed }
fixed! { "A 128-bit fixed-point signed integer", FixedI128(i128), Signed } fixed! { "A 128-bit fixed-point signed integer", FixedI128(i128), Signed }
trait MulDiv { trait MulDivDir: Sized {
fn mul(self, rhs: Self) -> Self; fn mul_dir(self, rhs: Self) -> (Self, Ordering);
fn div(self, rhs: Self) -> Self; fn div_dir(self, rhs: Self) -> (Self, Ordering);
} }
macro_rules! mul_div_widen { macro_rules! mul_div_widen {
($Single:ty, $Double:ty) => { ($Single:ty, $Double:ty, $Signedness:tt) => {
impl MulDiv for $Single { impl MulDivDir for $Single {
#[inline] #[inline]
fn mul(self, rhs: $Single) -> $Single { fn mul_dir(self, rhs: $Single) -> ($Single, Ordering) {
const BITS: u32 = mem::size_of::<$Single>() as u32 * 8; const BITS: u32 = mem::size_of::<$Single>() as u32 * 8;
const I: u32 = BITS - F; const I: u32 = BITS - F;
let lhs2 = self as $Double; let lhs2 = self as $Double;
let rhs2 = rhs as $Double << I; let rhs2 = rhs as $Double << I;
let prod2 = lhs2 * rhs2; let (prod2, overflow) = lhs2.overflowing_mul(rhs2);
(prod2 >> BITS) as $Single let dir;
if_unsigned! { $Signedness => {
dir = if !overflow {
Ordering::Equal
} else {
Ordering::Less
};
} }
if_signed! { $Signedness => {
dir = if !overflow {
Ordering::Equal
} else if (self < 0) == (rhs < 0) {
Ordering::Less
} else {
Ordering::Greater
};
} }
((prod2 >> BITS) as $Single, dir)
} }
#[inline] #[inline]
fn div(self, rhs: $Single) -> $Single { fn div_dir(self, rhs: $Single) -> ($Single, Ordering) {
let lhs2 = self as $Double << F; let lhs2 = self as $Double << F;
let rhs2 = rhs as $Double; let rhs2 = rhs as $Double;
let quot2 = lhs2 / rhs2; let quot2 = lhs2 / rhs2;
let quot = quot2 as $Single; let quot = quot2 as $Single;
debug_assert!(quot as $Double == quot2, "overflow"); let dir = (quot as $Double).cmp(&quot2);
quot (quot, dir)
} }
} }
}; };
@ -527,7 +799,7 @@ trait FallbackHelper: Sized {
fn hi_lo(self) -> (Self, Self); fn hi_lo(self) -> (Self, Self);
fn shift_lo_up(self) -> Self; fn shift_lo_up(self) -> Self;
fn shift_lo_up_unsigned(self) -> Self::Unsigned; fn shift_lo_up_unsigned(self) -> Self::Unsigned;
fn combine_lo_then_shl(self, lo: Self::Unsigned, shift: u32) -> Self; fn combine_lo_then_shl(self, lo: Self::Unsigned, shift: u32) -> (Self, Ordering);
fn carrying_add(self, other: Self) -> (Self, Self); fn carrying_add(self, other: Self) -> (Self, Self);
} }
@ -551,18 +823,16 @@ impl FallbackHelper for u128 {
} }
#[inline] #[inline]
fn combine_lo_then_shl(self, lo: u128, shift: u32) -> u128 { fn combine_lo_then_shl(self, lo: u128, shift: u32) -> (u128, Ordering) {
if shift == 128 { if shift == 128 {
return self; return (self, Ordering::Equal);
} }
if shift == 0 { if shift == 0 {
debug_assert!(self == 0, "overflow"); return (lo, 0.cmp(&self));
return lo;
} }
let lo = lo >> shift; let lo = lo >> shift;
let hi = self << (128 - shift); let hi = self << (128 - shift);
debug_assert!(self >> shift == 0, "overflow"); (lo | hi, 0.cmp(&(self >> shift)))
lo | hi
} }
#[inline] #[inline]
@ -593,20 +863,18 @@ impl FallbackHelper for i128 {
} }
#[inline] #[inline]
fn combine_lo_then_shl(self, lo: u128, shift: u32) -> i128 { fn combine_lo_then_shl(self, lo: u128, shift: u32) -> (i128, Ordering) {
if shift == 128 { if shift == 128 {
return self; return (self, Ordering::Equal);
} }
if shift == 0 { if shift == 0 {
let ans = lo as i128; let ans = lo as i128;
debug_assert!(ans >> 64 >> 64 == self, "overflow"); return (ans, (ans >> 64 >> 64).cmp(&self));
return ans;
} }
let lo = (lo >> shift) as i128; let lo = (lo >> shift) as i128;
let hi = self << (128 - shift); let hi = self << (128 - shift);
let ans = lo | hi; let ans = lo | hi;
debug_assert!(ans >> 64 >> 64 == self >> shift, "overflow"); (ans, (ans >> 64 >> 64).cmp(&(self >> shift)))
ans
} }
#[inline] #[inline]
@ -626,11 +894,29 @@ impl FallbackHelper for i128 {
} }
macro_rules! mul_div_fallback { macro_rules! mul_div_fallback {
($Single:ty) => { ($Single:ty, $Signedness:tt) => {
impl MulDiv for $Single { impl MulDivDir for $Single {
fn mul(self, rhs: $Single) -> $Single { fn mul_dir(self, rhs: $Single) -> ($Single, Ordering) {
if F == 0 { if F == 0 {
self * rhs let (ans, overflow) = self.overflowing_mul(rhs);
let dir;
if_unsigned! { $Signedness => {
dir = if !overflow {
Ordering::Equal
} else {
Ordering::Less
};
} }
if_signed! { $Signedness => {
dir = if !overflow {
Ordering::Equal
} else if (self < 0) == (rhs < 0) {
Ordering::Less
} else {
Ordering::Greater
};
} }
(ans, dir)
} else { } else {
let (lh, ll) = self.hi_lo(); let (lh, ll) = self.hi_lo();
let (rh, rl) = rhs.hi_lo(); let (rh, rl) = rhs.hi_lo();
@ -651,10 +937,27 @@ macro_rules! mul_div_fallback {
} }
} }
#[inline] fn div_dir(self, rhs: $Single) -> ($Single, Ordering) {
fn div(self, rhs: $Single) -> $Single {
if F == 0 { if F == 0 {
self / rhs let (ans, overflow) = self.overflowing_div(rhs);
let dir;
if_unsigned! { $Signedness => {
dir = if !overflow {
Ordering::Equal
} else {
Ordering::Less
};
} }
if_signed! { $Signedness => {
dir = if !overflow {
Ordering::Equal
} else if (self < 0) == (rhs < 0) {
Ordering::Less
} else {
Ordering::Greater
};
} }
(ans, dir)
} else { } else {
unimplemented!() unimplemented!()
} }
@ -663,16 +966,16 @@ macro_rules! mul_div_fallback {
}; };
} }
mul_div_widen! { u8, u16 } mul_div_widen! { u8, u16, Unsigned }
mul_div_widen! { u16, u32 } mul_div_widen! { u16, u32, Unsigned }
mul_div_widen! { u32, u64 } mul_div_widen! { u32, u64, Unsigned }
mul_div_widen! { u64, u128 } mul_div_widen! { u64, u128, Unsigned }
mul_div_fallback! { u128 } mul_div_fallback! { u128, Unsigned }
mul_div_widen! { i8, i16 } mul_div_widen! { i8, i16, Signed }
mul_div_widen! { i16, i32 } mul_div_widen! { i16, i32, Signed }
mul_div_widen! { i32, i64 } mul_div_widen! { i32, i64, Signed }
mul_div_widen! { i64, i128 } mul_div_widen! { i64, i128, Signed }
mul_div_fallback! { i128 } mul_div_fallback! { i128, Signed }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {