commit 5e0844006e015c58b4a0d00d5ff654136ea6c15b Author: Timothy J Warren Date: Wed Jan 27 07:44:02 2021 -0500 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..33eaa4f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rusty-fib-facts" +version = "0.1.0" +authors = ["Timothy J Warren "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[dev-dependencies] +criterion = "0.3" + +[[bench]] +name = "stock_functions" +harness = false \ No newline at end of file diff --git a/benches/stock_functions.rs b/benches/stock_functions.rs new file mode 100644 index 0000000..91f76af --- /dev/null +++ b/benches/stock_functions.rs @@ -0,0 +1,109 @@ +use criterion::{criterion_group, criterion_main}; +#[cfg(not(tarpaulin_include))] +mod sf { + use criterion::{black_box, BenchmarkId, Criterion}; + use rusty_fib_facts::{UnsignedGCD, factorial, fibonacci, it_factorial, mem_fibonacci, rec_fibonacci}; + + pub fn bench_factorial(c: &mut Criterion) { + let mut group = c.benchmark_group("Factorials"); + + for i in [0usize, 5, 10, 15, 20, 25, 30, 34].iter() { + group.bench_with_input(BenchmarkId::new("Recursive naive", i), i, |b, i| { + b.iter(|| factorial(black_box(*i))) + }); + group.bench_with_input(BenchmarkId::new("Iterative", i), i, |b, i| { + b.iter(|| it_factorial(black_box(*i))) + }); + } + group.finish(); + } + + pub fn bench_fibonacci(c: &mut Criterion) { + let mut group = c.benchmark_group("Fibonacci"); + + for i in [0usize, 10, 20, 30, 40, 50, 70, 93, 140, 186].iter() { + group.bench_with_input(BenchmarkId::new("Recursive memoized", i), i, |b, i| { + b.iter(|| mem_fibonacci(black_box(*i))) + }); + group.bench_with_input(BenchmarkId::new("Iterative", i), i, |b, i| { + b.iter(|| fibonacci(black_box(*i))) + }); + } + + group.finish(); + + let mut group = c.benchmark_group("Recursive Fibonacci"); + for i in [0usize, 10, 20, 25, 26, 27, 28, 29, 30].iter() { + group.bench_with_input(BenchmarkId::new("Naive Recursive", i), i, |b, i| { + b.iter(|| rec_fibonacci(black_box(*i))) + }); + } + group.finish(); + } + + pub fn bench_gcd(c: &mut Criterion) { + let mut group = c.benchmark_group("GCD"); + + #[derive(Debug)] + struct Gcd { + left: u128, + right: u128, + left_fib: u128, + right_fib: u128, + } + + impl Gcd { + fn new(left: u128, right: u128) -> Self { + Gcd { + left, + right, + left_fib: fibonacci(left as usize).unwrap(), + right_fib: fibonacci(right as usize).unwrap(), + } + } + } + + let max = Gcd::new(185, 186); + let med = Gcd::new(92, 93); + let small = Gcd::new(14, 15); + + for input in [small, med, max].iter() { + group.bench_with_input( + BenchmarkId::new("Binary", input.left), + input, + |bench, input| { + let a = input.left_fib; + let b = input.right_fib; + bench.iter(|| u128::gcd(black_box(a), black_box(b))) + }, + ); + group.bench_with_input( + BenchmarkId::new("Stein", input.left), + input, + |bench, input| { + let a = input.left_fib; + let b = input.right_fib; + bench.iter(|| u128::stein_gcd(black_box(a), black_box(b))) + }, + ); + group.bench_with_input( + BenchmarkId::new("Euclid", input.left), + input, + |bench, input| { + let a = input.left_fib; + let b = input.right_fib; + bench.iter(|| u128::e_gcd(black_box(a), black_box(b))) + }, + ); + } + group.finish(); + } +} + +criterion_group!( + benches, + sf::bench_factorial, + sf::bench_fibonacci, + sf::bench_gcd, +); +criterion_main!(benches); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3a94681 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,405 @@ +//! # Rusty Fib Facts +//! +//! Implementations of common math algorithms to benchmark +#![forbid(unsafe_code)] +#![no_std] + +use core::cmp::{max, min}; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +#[macro_use] +extern crate alloc; + +#[cfg(feature = "std")] +#[macro_use] +extern crate std; + +#[cfg(feature = "std")] +use core::f64::consts::{E, PI}; + +/// Calculate a number in the fibonacci sequence, +/// using recursion and a lookup table +/// +/// Can calculate up to 186 using native unsigned 128 bit integers. +/// +/// Example: +/// ```rust +/// use rusty_fib_facts::mem_fibonacci; +/// +/// let valid = mem_fibonacci(45); // Some(1134903170) +/// # assert_eq!(1134903170, mem_fibonacci(45).unwrap()); +/// # assert!(valid.is_some()); +/// +/// let invalid = mem_fibonacci(187); // None +/// # assert!(invalid.is_none()); +/// ``` +#[inline] +pub fn mem_fibonacci(n: usize) -> Option { + let mut table = [0u128; 187]; + table[0] = 0; + table[1] = 1; + table[2] = 1; + + /// Actual calculating function for `fibonacci` + fn f(n: usize, table: &mut [u128]) -> Option { + if n < 2 { + // The first values are predefined. + return Some(table[n]); + } + + if n > 186 { + return None; + } + + match table[n] { + // The lookup array starts out zeroed, so a zero + // is a not yet calculated value + 0 => { + let a = f(n - 1, table)?; + let b = f(n - 2, table)?; + + table[n] = a + b; + + Some(table[n]) + } + x => Some(x), + } + } + + f(n, &mut table) +} + +/// Calculate a number in the fibonacci sequence, +/// using naive recursion +/// +/// REALLY SLOW +/// +/// Can calculate up to 186 using native unsigned 128 bit integers. +#[inline] +pub fn rec_fibonacci(n: usize) -> Option { + if matches!(n, 0 | 1) { + Some(n as u128) + } else { + let a = rec_fibonacci(n - 1)?; + let b = rec_fibonacci(n - 2)?; + + a.checked_add(b) + } +} + +/// Calculate a number in the fibonacci sequence, +/// +/// Can calculate up to 186 using native unsigned 128 bit integers. +/// +/// Example: +/// ```rust +/// use rusty_fib_facts::fibonacci; +/// +/// let valid = fibonacci(45); // Some(1134903170) +/// # assert_eq!(1134903170, fibonacci(45).unwrap()); +/// # assert!(valid.is_some()); +/// +/// let invalid = fibonacci(187); // None +/// # assert!(invalid.is_none()); +/// ``` +#[inline] +pub fn fibonacci(n: usize) -> Option { + let mut a: u128 = 0; + let mut b: u128 = 1; + + if matches!(n, 0 | 1) { + Some(n as u128) + } else { + for _ in 0..n - 1 { + let c: u128 = a.checked_add(b)?; + + a = b; + b = c; + } + + Some(b) + } +} + +/// Calculate the value of a factorial iteratively +/// +/// Can calculate up to 34! using native unsigned 128 bit integers. +/// +/// Example: +/// ```rust +/// use rusty_fib_facts::factorial; +/// +/// let valid = factorial(3); // Some(6) +/// # assert_eq!(6, valid.unwrap()); +/// +/// let invalid = factorial(35); // None +/// # assert!(invalid.is_none()); +/// ``` +#[inline] +pub fn it_factorial(n: usize) -> Option { + let mut total: u128 = 1; + + if matches!(n, 0 | 1) { + Some(1u128) + } else { + for x in 1..=n { + total = total.checked_mul(x as u128)?; + } + + Some(total) + } +} + +/// Calculate the value of a factorial recrursively +/// +/// Can calculate up to 34! using native unsigned 128 bit integers. +/// +/// Example: +/// ```rust +/// use rusty_fib_facts::factorial; +/// +/// let valid = factorial(3); // Some(6) +/// # assert_eq!(6, valid.unwrap()); +/// +/// let invalid = factorial(35); // None +/// # assert!(invalid.is_none()); +/// ``` +#[inline] +pub fn factorial(n: usize) -> Option { + if matches!(n, 0 | 1) { + Some(1u128) + } else { + let prev = factorial(n - 1)?; + + (n as u128).checked_mul(prev) + } +} + +/// Approximates a factorial using Stirling's approximation +/// +/// Based on https://en.wikipedia.org/wiki/Stirling's_approximation +/// +/// Can approximate up to ~ 170.624! +/// +/// Example: +/// ```rust +/// use rusty_fib_facts::approx_factorial; +/// +/// let valid = approx_factorial(170.6); // Some(..) +/// # assert!(valid.is_some()); +/// +/// let invalid = approx_factorial(171.0); // None +/// # assert!(invalid.is_none()); +/// ``` +#[cfg(feature = "std")] +#[inline] +pub fn approx_factorial(n: f64) -> Option { + let power = (n / E).powf(n); + let root = (PI * 2.0 * n).sqrt(); + + let res = power * root; + + if res >= core::f64::MIN && res <= core::f64::MAX { + Some(res) + } else { + None + } +} + +pub trait UnsignedGCD { + /// Find the greatest common denominator of two numbers + fn gcd(a: Self, b: Self) -> Self; + + /// Euclid gcd algorithm + fn e_gcd(a: Self, b: Self) -> Self; + + /// Stein gcd algorithm + fn stein_gcd(a: Self, b: Self) -> Self; + + /// Find the least common multiple of two numbers + fn lcm(a: Self, b: Self) -> Self; +} + +macro_rules! impl_unsigned { + ($($Type: ty),* ) => { + $( + impl UnsignedGCD for $Type { + /// Implementation based on + /// [Binary GCD algorithm](https://en.wikipedia.org/wiki/Binary_GCD_algorithm) + fn gcd(a: $Type, b: $Type) -> $Type { + if a == b { + return a; + } else if a == 0 { + return b; + } else if b == 0 { + return a; + } + + let a_even = a % 2 == 0; + let b_even = b % 2 == 0; + + if a_even { + if b_even { + // Both a & b are even + return Self::gcd(a >> 1, b >> 1) << 1; + } else if !b_even { + // b is odd + return Self::gcd(a >> 1, b); + } + } + + // a is odd, b is even + if (!a_even) && b_even { + return Self::gcd(a, b >> 1); + } + + if a > b { + return Self::gcd((a - b) >> 1, b); + } + + Self::gcd((b - a) >> 1, a) + } + + fn e_gcd(x: $Type, y: $Type) -> $Type { + let mut x = x; + let mut y = y; + while y != 0 { + let t = y; + y = x % y; + x = t; + } + x + } + + fn stein_gcd(a: Self, b: Self) -> Self { + match ((a, b), (a & 1, b & 1)) { + ((x, y), _) if x == y => y, + ((0, x), _) | ((x, 0), _) => x, + ((x, y), (0, 1)) | ((y, x), (1, 0)) => Self::stein_gcd(x >> 1, y), + ((x, y), (0, 0)) => Self::stein_gcd(x >> 1, y >> 1) << 1, + ((x, y), (1, 1)) => { + let (x, y) = (min(x, y), max(x, y)); + Self::stein_gcd((y - x) >> 1, x) + } + _ => unreachable!(), + } + } + + fn lcm(a: $Type, b: $Type) -> $Type { + if (a == 0 && b == 0) { + return 0; + } + + a * b / Self::gcd(a, b) + } + } + )* + }; +} + +impl_unsigned!(u8, u16, u32, u64, u128, usize); + +#[cfg(test)] +#[cfg(not(tarpaulin_include))] +mod tests { + use super::*; + + #[test] + fn test_factorial() { + for pair in [[1usize, 0], [1, 1], [2, 2], [6, 3]].iter() { + assert_eq!( + Some(pair[0] as u128), + factorial(pair[1]), + "{}! should be {}", + pair[1], + pair[0] + ); + assert_eq!( + Some(pair[0] as u128), + it_factorial(pair[1]), + "{}! should be {}", + pair[1], + pair[0] + ); + } + + // Verify each implementation returns the same results + let res = factorial(34); + let res2 = it_factorial(34); + assert!(res.is_some()); + assert!(res2.is_some()); + assert_eq!(res, res2); + + // Bounds checks + assert!(factorial(35).is_none()); + assert!(it_factorial(35).is_none()); + } + + #[cfg(feature = "std")] + #[test] + fn test_approx_factorial() { + assert!(approx_factorial(170.624).is_some()); + assert!(approx_factorial(1.0).is_some()); + assert!(approx_factorial(170.7).is_none()); + } + + #[test] + fn test_fibonacci() { + // Sanity checking + for pair in [[0usize, 0], [1, 1], [1, 2], [2, 3]].iter() { + assert_eq!( + Some(pair[0] as u128), + fibonacci(pair[1]), + "fibonacci({}) should be {}", + pair[1], + pair[0] + ); + assert_eq!( + Some(pair[0] as u128), + mem_fibonacci(pair[1]), + "fibonacci({}) should be {}", + pair[1], + pair[0] + ); + assert_eq!( + Some(pair[0] as u128), + rec_fibonacci(pair[1]), + "fibonacci({}) should be {}", + pair[1], + pair[0] + ); + } + + // Verify each implementation returns the same results + let res = fibonacci(186); + let res2 = mem_fibonacci(186); + assert!(res.is_some()); + assert!(res2.is_some()); + assert_eq!(res, res2); + + // Bounds checks + assert!(fibonacci(187).is_none()); + assert!(mem_fibonacci(187).is_none()); + } + + #[test] + fn test_gcd() { + assert_eq!(u8::gcd(5, 0), 5); + assert_eq!(u8::gcd(0, 5), 5); + assert_eq!(u8::gcd(2, 3), 1); + assert_eq!(u8::gcd(2, 2), 2); + assert_eq!(u8::gcd(2, 8), 2); + assert_eq!(u16::gcd(36, 48), 12); + assert_eq!(u16::e_gcd(36, 48), 12); + assert_eq!(u16::stein_gcd(36, 48), 12); + } + + #[test] + fn test_lcm() { + assert_eq!(u32::lcm(2, 8), 8); + assert_eq!(u16::lcm(2, 3), 6); + assert_eq!(usize::lcm(15, 30), 30); + assert_eq!(u128::lcm(1, 5), 5); + assert_eq!(0u8, u8::lcm(0, 0)); + } +}