parity.rs

#
use std::{
    convert::From,
    ops::{AddAssign, BitAnd, BitAndAssign, BitXorAssign, Shr, ShrAssign},
};

use lazy_static::lazy_static;
use num_traits::PrimInt;
#

count the number of even/odd bits Takes O(n) time, where n is the width of the word Also somewhat slow: test primitive::parity::tests::bench_parity1 ... bench: 300,663 ns/iter (+/- 8,471)

pub fn count_bits<N: PrimInt + ShrAssign + AddAssign>(mut x: N) -> N {
    let mut result = N::zero();
    while x > N::zero() {
        result += x & N::one();
        x >>= N::one();
    }
    result
}
#

Takes O(k) time, where k is the number of bits set to 1 (can be n). The book says this is faster, and on my computer it is. test primitive::parity::tests::bench_parity2 ... bench: 177,997 ns/iter (+/- 3,596)

pub fn parity2<N: PrimInt + ShrAssign + BitXorAssign + BitAndAssign>(mut x: N) -> N {
    let mut result = N::zero();
    while x > N::zero() {
        result ^= N::one();
        x &= (x - N::one());
    }
    result
}
#

This one is O(log(n)) time, since it divides and conquers the input. test primitive::parity::tests::bench_parity4 ... bench: 61,933 ns/iter (+/- 514)

pub fn parity3<N: PrimInt + BitXorAssign + BitAnd>(mut x: N) -> N {
    x ^= x >> 32;
    x ^= x >> 16;
    x ^= x >> 8;
    x ^= x >> 4;
    x ^= x >> 2;
    x ^= x >> 1;

    x & N::one()
}
#

Using the intrinsics builtin. This is the fastest by far. test primitive::parity::tests::bench_parity5 ... bench: 53,765 ns/iter (+/- 10,017)

pub fn parity4<N: PrimInt + From<u32>>(mut x: N) -> N {
    if x.count_ones() % 2 != 0 {
        1.into()
    } else {
        0.into()
    }
}

#[cfg(test)]
mod tests {
    use crate::PEAK_ALLOC;

    use super::{count_bits, parity2, parity3, parity4};
    use quickcheck_macros::quickcheck;
    use test::{black_box, Bencher};

    #[quickcheck]
    fn count_bits_is_correct(num: u64) -> bool {
        count_bits(num) as u32 == num.count_ones()
    }

    #[quickcheck]
    fn parity2_is_correct(num: u64) -> bool {
        parity2(num) == parity4(num)
    }

    #[quickcheck]
    fn parity3_is_correct(num: u64) -> bool {
        parity3(num) == parity4(num)
    }

    #[test]
    fn memory_usage_count_bits() {
        for i in 0..=u16::MAX {
            count_bits(i as u64);
        }
        println!("{}", PEAK_ALLOC.peak_usage());
    }

    #[test]
    fn memory_usage_parity_2() {
        for i in 0..=u16::MAX {
            parity2(i as u64);
        }
        println!("{}", PEAK_ALLOC.peak_usage());
    }

    #[test]
    fn memory_usage_parity_3() {
        for i in 0..=u16::MAX {
            parity3(i as u64);
        }
        println!("{}", PEAK_ALLOC.peak_usage());
    }

    #[test]
    fn memory_usage_parity_4() {
        for i in 0..=u16::MAX {
            parity4(i as u64);
        }
        println!("{}", PEAK_ALLOC.peak_usage());
    }

    #[bench]
    fn bench_parity1(b: &mut Bencher) {
        b.iter(|| {
            for i in 0..=u16::MAX {
                black_box(count_bits(i as u64));
            }
        })
    }

    #[bench]
    fn bench_parity2(b: &mut Bencher) {
        b.iter(|| {
            for i in 0..=u16::MAX {
                black_box(parity2(i as u64));
            }
        })
    }

    #[bench]
    fn bench_parity3(b: &mut Bencher) {
        b.iter(|| {
            for i in 0..=u16::MAX {
                black_box(parity3(i as u64));
            }
        })
    }

    #[bench]
    fn bench_parity4(b: &mut Bencher) {
        b.iter(|| {
            for i in 0..=u16::MAX {
                black_box(parity4(i as u64));
            }
        })
    }
}