From 19ab37c59d038e05f34d7817dd3ddd2c490d982c Mon Sep 17 00:00:00 2001 From: Martin Larralde Date: Tue, 14 Jan 2020 17:45:40 +0100 Subject: [PATCH 1/8] Add `Average` trait with fast average computation --- benches/average.rs | 190 +++++++++++++++++++++++++++++++++++++++++++++ src/average.rs | 74 ++++++++++++++++++ src/lib.rs | 4 + 3 files changed, 268 insertions(+) create mode 100644 benches/average.rs create mode 100644 src/average.rs diff --git a/benches/average.rs b/benches/average.rs new file mode 100644 index 0000000..a53aedc --- /dev/null +++ b/benches/average.rs @@ -0,0 +1,190 @@ +//! Benchmark sqrt and cbrt + +#![feature(test)] + +extern crate num_integer; +extern crate num_traits; +extern crate test; + +use num_integer::Integer; +use num_traits::{AsPrimitive, PrimInt, WrappingAdd, WrappingMul}; +use test::{black_box, Bencher}; + +trait BenchInteger: Integer + PrimInt + WrappingAdd + WrappingMul + 'static {} + +impl BenchInteger for T where T: Integer + PrimInt + WrappingAdd + WrappingMul + 'static {} + +trait NaiveAverage { + fn naive_average_floor(&self, other: &Self) -> Self; + fn naive_average_ceil(&self, other: &Self) -> Self; +} + +trait UncheckedAverage { + fn unchecked_average_floor(&self, other: &Self) -> Self; + fn unchecked_average_ceil(&self, other: &Self) -> Self; +} + +fn bench(b: &mut Bencher, v: &[(T, T)], f: F) +where + T: Integer, + F: Fn(&T, &T) -> T, +{ + b.iter(|| { + for (x, y) in v { + black_box(f(x, y)); + } + }); +} + +// Simple PRNG so we don't have to worry about rand compatibility +fn lcg(x: T) -> T +where + u32: AsPrimitive, + T: BenchInteger, +{ + // LCG parameters from Numerical Recipes + // (but we're applying it to arbitrary sizes) + const LCG_A: u32 = 1664525; + const LCG_C: u32 = 1013904223; + x.wrapping_mul(&LCG_A.as_()).wrapping_add(&LCG_C.as_()) +} + +macro_rules! naive_average { + ($T:ident) => { + impl super::NaiveAverage for $T { + fn naive_average_floor(&self, other: &$T) -> $T { + match self.checked_add(*other) { + Some(z) => z/2, + None => if self > other { + let diff = self - other; + other + diff/2 + } else { + let diff = other - self; + self + diff/2 + } + } + } + fn naive_average_ceil(&self, other: &$T) -> $T { + match self.checked_add(*other).and_then(|x| x.checked_add(1)) { + Some(z) => z/2, + None => if self > other { + let diff = self - other; + self - diff/2 + } else { + let diff = other - self; + other - diff/2 + } + } + } + } + }; +} + +macro_rules! unchecked_average { + ($T:ident) => { + impl super::UncheckedAverage for $T { + fn unchecked_average_floor(&self, other: &$T) -> $T { + self.saturating_add(*other) / 2 + } + fn unchecked_average_ceil(&self, other: &$T) -> $T { + self.saturating_add(*other) / 2 + } + } + }; +} + +macro_rules! bench_average { + ($($T:ident),*) => {$( + mod $T { + use test::Bencher; + use num_integer::Average; + use UncheckedAverage; + use NaiveAverage; + + naive_average!($T); + unchecked_average!($T); + + const SIZE: $T = 30; + + fn overflowing() -> Vec<($T, $T)> { + (($T::max_value()-SIZE)..$T::max_value()) + .flat_map(|x| -> Vec<_> { + (($T::max_value()-100)..($T::max_value()-100+SIZE)) + .map(|y| (x, y)) + .collect() + }) + .collect() + } + + fn small() -> Vec<($T, $T)> { + (0..SIZE) + .flat_map(|x| -> Vec<_> {(0..SIZE).map(|y| (x, y)).collect()}) + .collect() + } + + fn rand() -> Vec<($T, $T)> { + small() + .into_iter() + .map(|(x, y)| (super::lcg(x), super::lcg(y))) + .collect() + } + + #[bench] + fn average_floor_small(b: &mut Bencher) { + let v = small(); + super::bench(b, &v, |x: &$T, y: &$T| x.average_floor(y)); + } + + #[bench] + fn average_floor_small_naive(b: &mut Bencher) { + let v = small(); + super::bench(b, &v, |x: &$T, y: &$T| x.naive_average_floor(y)); + } + + #[bench] + fn average_floor_small_unchecked(b: &mut Bencher) { + let v = small(); + super::bench(b, &v, |x: &$T, y: &$T| x.unchecked_average_floor(y)); + } + + #[bench] + fn average_floor_overflowing(b: &mut Bencher) { + let v = overflowing(); + super::bench(b, &v, |x: &$T, y: &$T| x.average_floor(y)); + } + + #[bench] + fn average_floor_overflowing_naive(b: &mut Bencher) { + let v = overflowing(); + super::bench(b, &v, |x: &$T, y: &$T| x.naive_average_floor(y)); + } + + #[bench] + fn average_floor_overflowing_unchecked(b: &mut Bencher) { + let v = overflowing(); + super::bench(b, &v, |x: &$T, y: &$T| x.unchecked_average_floor(y)); + } + + #[bench] + fn average_floor_rand(b: &mut Bencher) { + let v = rand(); + super::bench(b, &v, |x: &$T, y: &$T| x.average_floor(y)); + } + + #[bench] + fn average_floor_rand_naive(b: &mut Bencher) { + let v = rand(); + super::bench(b, &v, |x: &$T, y: &$T| x.naive_average_floor(y)); + } + + #[bench] + fn average_floor_rand_unchecked(b: &mut Bencher) { + let v = rand(); + super::bench(b, &v, |x: &$T, y: &$T| x.unchecked_average_floor(y)); + } + } + )*} +} + +bench_average!(i8, i16, i32, i64, i128, isize); +bench_average!(u8, u16, u32, u64, u128, usize); diff --git a/src/average.rs b/src/average.rs new file mode 100644 index 0000000..1f95fa5 --- /dev/null +++ b/src/average.rs @@ -0,0 +1,74 @@ +use core::ops::{Add, BitAnd, BitOr, BitXor, Shr, Sub}; +use Integer; + +/// Provides methods to compute the average of two integers, without overflows. +pub trait Average: Integer { + /// Returns the ceiling value of the average of `self` and `other`. + /// -- `⌈(self + other)/2⌉` + /// + /// # Examples + /// + /// ``` + /// use num_integer::Average; + /// + /// assert_eq!(( 3).average_ceil(&10), 7); + /// assert_eq!((-2).average_ceil(&-5), -3); + /// assert_eq!(( 4).average_ceil(& 4), 4); + /// + /// assert_eq!(u8::max_value().average_ceil(&2), 129); + /// ``` + /// + fn average_ceil(&self, other: &Self) -> Self; + + /// Returns the floor value of the average of `self` and `other`. + /// -- `⌊(self + other)/2⌋` + /// + /// # Examples + /// + /// ``` + /// use num_integer::Average; + /// + /// assert_eq!(( 3).average_floor(&10), 6); + /// assert_eq!((-2).average_floor(&-5), -4); + /// assert_eq!(( 4).average_floor(& 4), 4); + /// + /// assert_eq!(u8::max_value().average_floor(&2), 128); + /// ``` + /// + fn average_floor(&self, other: &Self) -> Self; +} + +impl Average for I +where + I: Integer + Add + Sub + Shr, + for<'a, 'b> &'a I: + BitAnd<&'b I, Output = I> + BitOr<&'b I, Output = I> + BitXor<&'b I, Output = I>, +{ + // The Henry Gordon Dietz implementation as shown in the Hacker's Delight, + // see http://aggregate.org/MAGIC/#Average%20of%20Integers + + /// Returns the floor value of the average of `self` and `other`. + #[inline] + fn average_floor(&self, other: &I) -> I { + (self & other) + ((self ^ other) >> 1) + } + + /// Returns the ceil value of the average of `self` and `other`. + #[inline] + fn average_ceil(&self, other: &I) -> I { + (self | other) - ((self ^ other) >> 1) + } +} + +/// Returns the floor value of the average of `x` and `y` -- +/// see [Average::average_floor](trait.Average.html#tymethod.average_floor). +#[inline] +pub fn average_floor(x: &T, y: &T) -> T { + x.average_floor(y) +} +/// Returns the ceiling value of the average of `x` and `y` -- +/// see [Average::average_ceil](trait.Average.html#tymethod.average_ceil). +#[inline] +pub fn average_ceil(x: &T, y: &T) -> T { + x.average_ceil(y) +} diff --git a/src/lib.rs b/src/lib.rs index bda2981..0281954 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,10 @@ mod roots; pub use roots::Roots; pub use roots::{cbrt, nth_root, sqrt}; +mod average; +pub use average::Average; +pub use average::{average_ceil, average_floor}; + pub trait Integer: Sized + Num + PartialOrd + Ord + Eq { /// Floored integer division. /// From ed953c28f05dc3f5d5ad9f6ad4327a9b61feb45f Mon Sep 17 00:00:00 2001 From: Martin Larralde Date: Tue, 14 Jan 2020 17:48:09 +0100 Subject: [PATCH 2/8] Address some PR review comments in `benches` and `src` --- benches/average.rs | 4 ++-- src/average.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/benches/average.rs b/benches/average.rs index a53aedc..d1b42f0 100644 --- a/benches/average.rs +++ b/benches/average.rs @@ -84,10 +84,10 @@ macro_rules! unchecked_average { ($T:ident) => { impl super::UncheckedAverage for $T { fn unchecked_average_floor(&self, other: &$T) -> $T { - self.saturating_add(*other) / 2 + self.wrapping_add(*other) / 2 } fn unchecked_average_ceil(&self, other: &$T) -> $T { - self.saturating_add(*other) / 2 + (self.wrapping_add(*other) / 2).wrapping_add(1) } } }; diff --git a/src/average.rs b/src/average.rs index 1f95fa5..1002f98 100644 --- a/src/average.rs +++ b/src/average.rs @@ -63,12 +63,12 @@ where /// Returns the floor value of the average of `x` and `y` -- /// see [Average::average_floor](trait.Average.html#tymethod.average_floor). #[inline] -pub fn average_floor(x: &T, y: &T) -> T { +pub fn average_floor(x: T, y: T) -> T { x.average_floor(y) } /// Returns the ceiling value of the average of `x` and `y` -- /// see [Average::average_ceil](trait.Average.html#tymethod.average_ceil). #[inline] -pub fn average_ceil(x: &T, y: &T) -> T { +pub fn average_ceil(x: T, y: T) -> T { x.average_ceil(y) } From 46d32a4648d9c4774a5219ca2a1501bebaf26188 Mon Sep 17 00:00:00 2001 From: Martin Larralde Date: Tue, 14 Jan 2020 17:53:19 +0100 Subject: [PATCH 3/8] Remove uneeded trait requirements on `Average` blanket impl --- src/average.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/average.rs b/src/average.rs index 1002f98..73a274a 100644 --- a/src/average.rs +++ b/src/average.rs @@ -1,4 +1,4 @@ -use core::ops::{Add, BitAnd, BitOr, BitXor, Shr, Sub}; +use core::ops::{BitAnd, BitOr, BitXor, Shr}; use Integer; /// Provides methods to compute the average of two integers, without overflows. @@ -40,7 +40,7 @@ pub trait Average: Integer { impl Average for I where - I: Integer + Add + Sub + Shr, + I: Integer + Shr, for<'a, 'b> &'a I: BitAnd<&'b I, Output = I> + BitOr<&'b I, Output = I> + BitXor<&'b I, Output = I>, { @@ -64,11 +64,11 @@ where /// see [Average::average_floor](trait.Average.html#tymethod.average_floor). #[inline] pub fn average_floor(x: T, y: T) -> T { - x.average_floor(y) + x.average_floor(&y) } /// Returns the ceiling value of the average of `x` and `y` -- /// see [Average::average_ceil](trait.Average.html#tymethod.average_ceil). #[inline] pub fn average_ceil(x: T, y: T) -> T { - x.average_ceil(y) + x.average_ceil(&y) } From 5ae9c3e4a875bc36984ab0da52b2b6c5af279868 Mon Sep 17 00:00:00 2001 From: Martin Larralde Date: Tue, 14 Jan 2020 18:02:40 +0100 Subject: [PATCH 4/8] Run `cargo fmt` on source code --- benches/average.rs | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/benches/average.rs b/benches/average.rs index d1b42f0..969b1f7 100644 --- a/benches/average.rs +++ b/benches/average.rs @@ -54,25 +54,29 @@ macro_rules! naive_average { impl super::NaiveAverage for $T { fn naive_average_floor(&self, other: &$T) -> $T { match self.checked_add(*other) { - Some(z) => z/2, - None => if self > other { - let diff = self - other; - other + diff/2 - } else { - let diff = other - self; - self + diff/2 + Some(z) => z / 2, + None => { + if self > other { + let diff = self - other; + other + diff / 2 + } else { + let diff = other - self; + self + diff / 2 + } } } } fn naive_average_ceil(&self, other: &$T) -> $T { match self.checked_add(*other).and_then(|x| x.checked_add(1)) { - Some(z) => z/2, - None => if self > other { - let diff = self - other; - self - diff/2 - } else { - let diff = other - self; - other - diff/2 + Some(z) => z / 2, + None => { + if self > other { + let diff = self - other; + self - diff / 2 + } else { + let diff = other - self; + other - diff / 2 + } } } } From 7581fcb162ff20d28cdb5ad3f0655c158c02c2ec Mon Sep 17 00:00:00 2001 From: Martin Larralde Date: Mon, 3 Feb 2020 12:30:26 +0100 Subject: [PATCH 5/8] Refactor `benches/average.rs` and check results of methods that should be exact --- benches/average.rs | 264 ++++++++++++++++++++++++++++++++------------- 1 file changed, 191 insertions(+), 73 deletions(-) diff --git a/benches/average.rs b/benches/average.rs index 969b1f7..c1c4a52 100644 --- a/benches/average.rs +++ b/benches/average.rs @@ -6,36 +6,18 @@ extern crate num_integer; extern crate num_traits; extern crate test; +use std::cmp::{min, max}; +use std::fmt::Debug; use num_integer::Integer; use num_traits::{AsPrimitive, PrimInt, WrappingAdd, WrappingMul}; use test::{black_box, Bencher}; +// --- Utilities for RNG ---------------------------------------------------- + trait BenchInteger: Integer + PrimInt + WrappingAdd + WrappingMul + 'static {} impl BenchInteger for T where T: Integer + PrimInt + WrappingAdd + WrappingMul + 'static {} -trait NaiveAverage { - fn naive_average_floor(&self, other: &Self) -> Self; - fn naive_average_ceil(&self, other: &Self) -> Self; -} - -trait UncheckedAverage { - fn unchecked_average_floor(&self, other: &Self) -> Self; - fn unchecked_average_ceil(&self, other: &Self) -> Self; -} - -fn bench(b: &mut Bencher, v: &[(T, T)], f: F) -where - T: Integer, - F: Fn(&T, &T) -> T, -{ - b.iter(|| { - for (x, y) in v { - black_box(f(x, y)); - } - }); -} - // Simple PRNG so we don't have to worry about rand compatibility fn lcg(x: T) -> T where @@ -49,26 +31,43 @@ where x.wrapping_mul(&LCG_A.as_()).wrapping_add(&LCG_C.as_()) } +// --- Alt. Implementations ------------------------------------------------- + +trait NaiveAverage { + fn naive_average_ceil(&self, other: &Self) -> Self; + fn naive_average_floor(&self, other: &Self) -> Self; +} + +trait UncheckedAverage { + fn unchecked_average_ceil(&self, other: &Self) -> Self; + fn unchecked_average_floor(&self, other: &Self) -> Self; +} + +trait ModuloAverage { + fn modulo_average_ceil(&self, other: &Self) -> Self; + fn modulo_average_floor(&self, other: &Self) -> Self; +} + macro_rules! naive_average { ($T:ident) => { impl super::NaiveAverage for $T { fn naive_average_floor(&self, other: &$T) -> $T { match self.checked_add(*other) { - Some(z) => z / 2, + Some(z) => z.div_floor(&2), None => { if self > other { let diff = self - other; - other + diff / 2 + other + diff.div_floor(&2) } else { let diff = other - self; - self + diff / 2 + self + diff.div_floor(&2) } } } } fn naive_average_ceil(&self, other: &$T) -> $T { match self.checked_add(*other).and_then(|x| x.checked_add(1)) { - Some(z) => z / 2, + Some(z) => z.div_ceil(&2), None => { if self > other { let diff = self - other; @@ -97,16 +96,95 @@ macro_rules! unchecked_average { }; } +macro_rules! modulo_average { + ($T:ident) => { + impl super::ModuloAverage for $T { + fn modulo_average_ceil(&self, other: &$T) -> $T { + let (q1, r1) = self.div_mod_floor(&2); + let (q2, r2) = other.div_mod_floor(&2); + q1 + q2 + (r1 * r2) + } + fn modulo_average_floor(&self, other: &$T) -> $T { + let (q1, r1) = self.div_mod_floor(&2); + let (q2, r2) = other.div_mod_floor(&2); + q1 + q2 + (r1 * r2) + } + } + }; +} + +// --- Bench functions ------------------------------------------------------ + +fn bench_unchecked(b: &mut Bencher, v: &[(T, T)], f: F) +where + T: Integer + Debug + Copy, + F: Fn(&T, &T) -> T, +{ + b.iter(|| { + for (x, y) in v { + black_box(f(x, y)); + } + }); +} + +fn bench_ceil(b: &mut Bencher, v: &[(T, T)], f: F) +where + T: Integer + Debug + Copy, + F: Fn(&T, &T) -> T, +{ + for &(i, j) in v { + let rt = f(&i, &j); + let (a, b) = (min(i, j), max(i, j)); + if (b - a).is_even() { + assert_eq!(rt - a, b - rt); + } else { + assert_eq!(rt - a, b - rt + T::one()); + } + } + bench_unchecked(b, v, f); +} + +fn bench_floor(b: &mut Bencher, v: &[(T, T)], f: F) +where + T: Integer + Debug + Copy, + F: Fn(&T, &T) -> T, +{ + for &(i, j) in v { + let rt = f(&i, &j); + let (a, b) = (min(i, j), max(i, j)); + println!("{:?} + {:?} / 2 = {:?}", a, b, rt); + // if both number are the same sign, check rt is in the middle + if (a < T::zero()) == (b < T::zero()) { + if (b - a).is_even() { + assert_eq!(rt - a, b - rt); + } else { + assert_eq!(rt - a + T::one(), b - rt); + } + // if both number have a different sign, + } else { + if (a + b).is_even() { + assert_eq!(rt, (a + b) / (T::one() + T::one())) + } else { + assert_eq!(rt, (a + b - T::one()) / (T::one() + T::one())) + } + } + } + bench_unchecked(b, v, f); +} + +// --- Bench implementation ------------------------------------------------- + macro_rules! bench_average { ($($T:ident),*) => {$( mod $T { use test::Bencher; - use num_integer::Average; - use UncheckedAverage; - use NaiveAverage; + use num_integer::{Average, Integer}; + use super::{UncheckedAverage, NaiveAverage, ModuloAverage}; + use super::{bench_ceil, bench_floor, bench_unchecked}; naive_average!($T); unchecked_average!($T); + modulo_average!($T); const SIZE: $T = 30; @@ -133,59 +211,99 @@ macro_rules! bench_average { .collect() } - #[bench] - fn average_floor_small(b: &mut Bencher) { - let v = small(); - super::bench(b, &v, |x: &$T, y: &$T| x.average_floor(y)); - } + mod floor { - #[bench] - fn average_floor_small_naive(b: &mut Bencher) { - let v = small(); - super::bench(b, &v, |x: &$T, y: &$T| x.naive_average_floor(y)); - } + use super::*; - #[bench] - fn average_floor_small_unchecked(b: &mut Bencher) { - let v = small(); - super::bench(b, &v, |x: &$T, y: &$T| x.unchecked_average_floor(y)); - } + mod small { - #[bench] - fn average_floor_overflowing(b: &mut Bencher) { - let v = overflowing(); - super::bench(b, &v, |x: &$T, y: &$T| x.average_floor(y)); - } + use super::*; - #[bench] - fn average_floor_overflowing_naive(b: &mut Bencher) { - let v = overflowing(); - super::bench(b, &v, |x: &$T, y: &$T| x.naive_average_floor(y)); - } + #[bench] + fn optimized(b: &mut Bencher) { + let v = small(); + bench_floor(b, &v, |x: &$T, y: &$T| x.average_floor(y)); + } - #[bench] - fn average_floor_overflowing_unchecked(b: &mut Bencher) { - let v = overflowing(); - super::bench(b, &v, |x: &$T, y: &$T| x.unchecked_average_floor(y)); - } + #[bench] + fn naive(b: &mut Bencher) { + let v = small(); + bench_floor(b, &v, |x: &$T, y: &$T| x.naive_average_floor(y)); + } - #[bench] - fn average_floor_rand(b: &mut Bencher) { - let v = rand(); - super::bench(b, &v, |x: &$T, y: &$T| x.average_floor(y)); - } + #[bench] + fn unchecked(b: &mut Bencher) { + let v = small(); + bench_unchecked(b, &v, |x: &$T, y: &$T| x.unchecked_average_floor(y)); + } - #[bench] - fn average_floor_rand_naive(b: &mut Bencher) { - let v = rand(); - super::bench(b, &v, |x: &$T, y: &$T| x.naive_average_floor(y)); - } + #[bench] + fn modulo(b: &mut Bencher) { + let v = small(); + bench_floor(b, &v, |x: &$T, y: &$T| x.modulo_average_floor(y)); + } + } + + mod overflowing { + + use super::*; + + #[bench] + fn optimized(b: &mut Bencher) { + let v = overflowing(); + bench_floor(b, &v, |x: &$T, y: &$T| x.average_floor(y)); + } + + #[bench] + fn naive(b: &mut Bencher) { + let v = overflowing(); + bench_floor(b, &v, |x: &$T, y: &$T| x.naive_average_floor(y)); + } + + #[bench] + fn unchecked(b: &mut Bencher) { + let v = overflowing(); + bench_unchecked(b, &v, |x: &$T, y: &$T| x.unchecked_average_floor(y)); + } + + #[bench] + fn modulo(b: &mut Bencher) { + let v = overflowing(); + bench_floor(b, &v, |x: &$T, y: &$T| x.modulo_average_floor(y)); + } + } + + mod rand { + + use super::*; + + #[bench] + fn optimized(b: &mut Bencher) { + let v = rand(); + bench_floor(b, &v, |x: &$T, y: &$T| x.average_floor(y)); + } + + #[bench] + fn naive(b: &mut Bencher) { + let v = rand(); + bench_floor(b, &v, |x: &$T, y: &$T| x.naive_average_floor(y)); + } + + #[bench] + fn unchecked(b: &mut Bencher) { + let v = rand(); + bench_unchecked(b, &v, |x: &$T, y: &$T| x.unchecked_average_floor(y)); + } + + #[bench] + fn modulo(b: &mut Bencher) { + let v = rand(); + bench_floor(b, &v, |x: &$T, y: &$T| x.modulo_average_floor(y)); + } + } - #[bench] - fn average_floor_rand_unchecked(b: &mut Bencher) { - let v = rand(); - super::bench(b, &v, |x: &$T, y: &$T| x.unchecked_average_floor(y)); } + } )*} } From 67fca34e47922903692d243cdd402955fd6b61cf Mon Sep 17 00:00:00 2001 From: Martin Larralde Date: Mon, 3 Feb 2020 13:53:17 +0100 Subject: [PATCH 6/8] Add benchmarks for `average_ceil` as well --- benches/average.rs | 122 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 113 insertions(+), 9 deletions(-) diff --git a/benches/average.rs b/benches/average.rs index c1c4a52..6391f3b 100644 --- a/benches/average.rs +++ b/benches/average.rs @@ -6,10 +6,10 @@ extern crate num_integer; extern crate num_traits; extern crate test; -use std::cmp::{min, max}; -use std::fmt::Debug; use num_integer::Integer; use num_traits::{AsPrimitive, PrimInt, WrappingAdd, WrappingMul}; +use std::cmp::{max, min}; +use std::fmt::Debug; use test::{black_box, Bencher}; // --- Utilities for RNG ---------------------------------------------------- @@ -66,15 +66,15 @@ macro_rules! naive_average { } } fn naive_average_ceil(&self, other: &$T) -> $T { - match self.checked_add(*other).and_then(|x| x.checked_add(1)) { + match self.checked_add(*other) { Some(z) => z.div_ceil(&2), None => { if self > other { let diff = self - other; - self - diff / 2 + self - diff.div_floor(&2) } else { let diff = other - self; - other - diff / 2 + other - diff.div_floor(&2) } } } @@ -102,7 +102,7 @@ macro_rules! modulo_average { fn modulo_average_ceil(&self, other: &$T) -> $T { let (q1, r1) = self.div_mod_floor(&2); let (q2, r2) = other.div_mod_floor(&2); - q1 + q2 + (r1 * r2) + q1 + q2 + (r1 | r2) } fn modulo_average_floor(&self, other: &$T) -> $T { let (q1, r1) = self.div_mod_floor(&2); @@ -135,10 +135,21 @@ where for &(i, j) in v { let rt = f(&i, &j); let (a, b) = (min(i, j), max(i, j)); - if (b - a).is_even() { - assert_eq!(rt - a, b - rt); + println!("( {:?} + {:?} )/ 2 = {:?}", a, b, rt); + // if both number are the same sign, check rt is in the middle + if (a < T::zero()) == (b < T::zero()) { + if (b - a).is_even() { + assert_eq!(rt - a, b - rt); + } else { + assert_eq!(rt - a, b - rt + T::one()); + } + // if both number have a different sign, } else { - assert_eq!(rt - a, b - rt + T::one()); + if (a + b).is_even() { + assert_eq!(rt, (a + b) / (T::one() + T::one())) + } else { + assert_eq!(rt, (a + b + T::one()) / (T::one() + T::one())) + } } } bench_unchecked(b, v, f); @@ -211,6 +222,99 @@ macro_rules! bench_average { .collect() } + mod ceil { + + use super::*; + + mod small { + + use super::*; + + #[bench] + fn optimized(b: &mut Bencher) { + let v = small(); + bench_ceil(b, &v, |x: &$T, y: &$T| x.average_ceil(y)); + } + + #[bench] + fn naive(b: &mut Bencher) { + let v = small(); + bench_ceil(b, &v, |x: &$T, y: &$T| x.naive_average_ceil(y)); + } + + #[bench] + fn unchecked(b: &mut Bencher) { + let v = small(); + bench_unchecked(b, &v, |x: &$T, y: &$T| x.unchecked_average_ceil(y)); + } + + #[bench] + fn modulo(b: &mut Bencher) { + let v = small(); + bench_ceil(b, &v, |x: &$T, y: &$T| x.modulo_average_ceil(y)); + } + } + + mod overflowing { + + use super::*; + + #[bench] + fn optimized(b: &mut Bencher) { + let v = overflowing(); + bench_ceil(b, &v, |x: &$T, y: &$T| x.average_ceil(y)); + } + + #[bench] + fn naive(b: &mut Bencher) { + let v = overflowing(); + bench_ceil(b, &v, |x: &$T, y: &$T| x.naive_average_ceil(y)); + } + + #[bench] + fn unchecked(b: &mut Bencher) { + let v = overflowing(); + bench_unchecked(b, &v, |x: &$T, y: &$T| x.unchecked_average_ceil(y)); + } + + #[bench] + fn modulo(b: &mut Bencher) { + let v = overflowing(); + bench_ceil(b, &v, |x: &$T, y: &$T| x.modulo_average_ceil(y)); + } + } + + mod rand { + + use super::*; + + #[bench] + fn optimized(b: &mut Bencher) { + let v = rand(); + bench_ceil(b, &v, |x: &$T, y: &$T| x.average_ceil(y)); + } + + #[bench] + fn naive(b: &mut Bencher) { + let v = rand(); + bench_ceil(b, &v, |x: &$T, y: &$T| x.naive_average_ceil(y)); + } + + #[bench] + fn unchecked(b: &mut Bencher) { + let v = rand(); + bench_unchecked(b, &v, |x: &$T, y: &$T| x.unchecked_average_ceil(y)); + } + + #[bench] + fn modulo(b: &mut Bencher) { + let v = rand(); + bench_ceil(b, &v, |x: &$T, y: &$T| x.modulo_average_ceil(y)); + } + } + + } + mod floor { use super::*; From 85fd7c8c7a63989200014224f8409641fbf21cf7 Mon Sep 17 00:00:00 2001 From: Martin Larralde Date: Mon, 3 Feb 2020 14:22:13 +0100 Subject: [PATCH 7/8] Add proper unit tests for `average_ceil` and `average_floor` --- benches/average.rs | 2 - tests/average.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 tests/average.rs diff --git a/benches/average.rs b/benches/average.rs index 6391f3b..05d824c 100644 --- a/benches/average.rs +++ b/benches/average.rs @@ -135,7 +135,6 @@ where for &(i, j) in v { let rt = f(&i, &j); let (a, b) = (min(i, j), max(i, j)); - println!("( {:?} + {:?} )/ 2 = {:?}", a, b, rt); // if both number are the same sign, check rt is in the middle if (a < T::zero()) == (b < T::zero()) { if (b - a).is_even() { @@ -163,7 +162,6 @@ where for &(i, j) in v { let rt = f(&i, &j); let (a, b) = (min(i, j), max(i, j)); - println!("{:?} + {:?} / 2 = {:?}", a, b, rt); // if both number are the same sign, check rt is in the middle if (a < T::zero()) == (b < T::zero()) { if (b - a).is_even() { diff --git a/tests/average.rs b/tests/average.rs new file mode 100644 index 0000000..9fd8cf1 --- /dev/null +++ b/tests/average.rs @@ -0,0 +1,100 @@ +extern crate num_integer; +extern crate num_traits; + +macro_rules! test_average { + ($I:ident, $U:ident) => { + mod $I { + mod ceil { + use num_integer::Average; + + #[test] + fn same_sign() { + assert_eq!((14 as $I).average_ceil(&16), 15 as $I); + assert_eq!((14 as $I).average_ceil(&17), 16 as $I); + + let max = $crate::std::$I::MAX; + assert_eq!((max - 3).average_ceil(&(max - 1)), max - 2); + assert_eq!((max - 3).average_ceil(&(max - 2)), max - 2); + } + + #[test] + fn different_sign() { + assert_eq!((14 as $I).average_ceil(&-4), 5 as $I); + assert_eq!((14 as $I).average_ceil(&-5), 5 as $I); + + let min = $crate::std::$I::MIN; + let max = $crate::std::$I::MAX; + assert_eq!(min.average_ceil(&max), 0 as $I); + } + } + + mod floor { + use num_integer::Average; + + #[test] + fn same_sign() { + assert_eq!((14 as $I).average_floor(&16), 15 as $I); + assert_eq!((14 as $I).average_floor(&17), 15 as $I); + + let max = $crate::std::$I::MAX; + assert_eq!((max - 3).average_floor(&(max - 1)), max - 2); + assert_eq!((max - 3).average_floor(&(max - 2)), max - 3); + } + + #[test] + fn different_sign() { + assert_eq!((14 as $I).average_floor(&-4), 5 as $I); + assert_eq!((14 as $I).average_floor(&-5), 4 as $I); + + let min = $crate::std::$I::MIN; + let max = $crate::std::$I::MAX; + assert_eq!(min.average_floor(&max), -1 as $I); + } + } + } + + mod $U { + mod ceil { + use num_integer::Average; + + #[test] + fn bounded() { + assert_eq!((14 as $U).average_ceil(&16), 15 as $U); + assert_eq!((14 as $U).average_ceil(&17), 16 as $U); + } + + #[test] + fn overflow() { + let max = $crate::std::$U::MAX; + assert_eq!((max - 3).average_ceil(&(max - 1)), max - 2); + assert_eq!((max - 3).average_ceil(&(max - 2)), max - 2); + } + } + + mod floor { + use num_integer::Average; + + #[test] + fn bounded() { + assert_eq!((14 as $U).average_floor(&16), 15 as $U); + assert_eq!((14 as $U).average_floor(&17), 15 as $U); + } + + #[test] + fn overflow() { + let max = $crate::std::$U::MAX; + assert_eq!((max - 3).average_floor(&(max - 1)), max - 2); + assert_eq!((max - 3).average_floor(&(max - 2)), max - 3); + } + } + } + }; +} + +test_average!(i8, u8); +test_average!(i16, u16); +test_average!(i32, u32); +test_average!(i64, u64); +#[cfg(has_i128)] +test_average!(i128, u128); +test_average!(isize, usize); From 1b1bf85811534c1a10643239cb99a013e93b16ad Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 6 Mar 2020 15:57:15 -0800 Subject: [PATCH 8/8] Test boundary averages of i8 --- src/average.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/average.rs b/src/average.rs index 73a274a..29cd11e 100644 --- a/src/average.rs +++ b/src/average.rs @@ -16,6 +16,8 @@ pub trait Average: Integer { /// assert_eq!(( 4).average_ceil(& 4), 4); /// /// assert_eq!(u8::max_value().average_ceil(&2), 129); + /// assert_eq!(i8::min_value().average_ceil(&-1), -64); + /// assert_eq!(i8::min_value().average_ceil(&i8::max_value()), 0); /// ``` /// fn average_ceil(&self, other: &Self) -> Self; @@ -33,6 +35,8 @@ pub trait Average: Integer { /// assert_eq!(( 4).average_floor(& 4), 4); /// /// assert_eq!(u8::max_value().average_floor(&2), 128); + /// assert_eq!(i8::min_value().average_floor(&-1), -65); + /// assert_eq!(i8::min_value().average_floor(&i8::max_value()), -1); /// ``` /// fn average_floor(&self, other: &Self) -> Self;