Skip to content

Add Average trait with integer average computation #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions benches/average.rs
Original file line number Diff line number Diff line change
@@ -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<T> 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<T, F>(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<T>(x: T) -> T
where
u32: AsPrimitive<T>,
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);
74 changes: 74 additions & 0 deletions src/average.rs
Original file line number Diff line number Diff line change
@@ -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<I> Average for I
where
I: Integer + Add<I, Output = I> + Sub<I, Output = I> + Shr<usize, Output = I>,
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<T: Average>(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<T: Average>(x: &T, y: &T) -> T {
x.average_ceil(y)
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,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.
///
Expand Down