diff --git a/benches/stock_functions.rs b/benches/stock_functions.rs index 46bb1b7..2255c29 100644 --- a/benches/stock_functions.rs +++ b/benches/stock_functions.rs @@ -1,16 +1,16 @@ use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; -use rusty_numbers::{factorial, fibonacci, mem_fibonacci, mem_factorial}; +use rusty_numbers::{factorial, fibonacci, it_factorial, mem_fibonacci, rec_fibonacci}; 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 memoized", i), i, |b, i| { - b.iter(|| mem_factorial(*i)) - }); group.bench_with_input(BenchmarkId::new("Recursive naive", i), i, |b, i| { b.iter(|| factorial(*i)) }); + group.bench_with_input(BenchmarkId::new("Iterative", i), i, |b, i| { + b.iter(|| it_factorial(*i)) + }); } group.finish(); } @@ -27,6 +27,16 @@ fn bench_fibonacci(c: &mut Criterion) { b.iter(|| fibonacci(*i)) }); } + + group.finish(); + + let mut group = c.benchmark_group("Recursive Fibonacci"); + for i in [0usize, 5, 10, 15, 20, 25, 26, 27, 28, 29, 30].iter() { + group.bench_with_input(BenchmarkId::new("Naive Recursive", i), i, |b, i| { + b.iter(|| rec_fibonacci(*i)) + }); + } + group.finish(); } criterion_group!(benches, bench_factorial, bench_fibonacci); diff --git a/src/lib.rs b/src/lib.rs index ae8bcd8..d36cee8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,11 @@ pub fn mem_fibonacci(n: usize) -> Option { /// Actual calculating function for `fibonacci` #[inline] fn _mem_fibonacci(n: usize, table: &mut [u128]) -> Option { + if n < 2 { + // The first values are predefined. + return Some(table[n]); + } + if n > 186 { return None; } @@ -51,8 +56,8 @@ fn _mem_fibonacci(n: usize, table: &mut [u128]) -> Option { table[n] = a + b; Some(table[n]) - }, - x => Some(x) + } + x => Some(x), } } @@ -101,8 +106,7 @@ pub fn fibonacci(n: usize) -> Option { } } -/// Calculate the value of a factorial, -/// using a lookup table for better worst-case performance. +/// Calculate the value of a factorial iteratively /// /// Can calculate up to 34! using native unsigned 128 bit integers. /// @@ -117,40 +121,22 @@ pub fn fibonacci(n: usize) -> Option { /// # assert!(invalid.is_none()); /// ``` #[inline] -pub fn mem_factorial(n: usize) -> Option { - let mut table = [0u128; 35]; - table[0] = 1; - table[1] = 1; - table[2] = 2; +pub fn it_factorial(n: usize) -> Option { + let mut total: u128 = 1; + match n { + 0 | 1 => Some(1u128), + _ => { + for x in 1..=n { + total = total.checked_mul(x as u128)?; + } - _mem_factorial(n, &mut table) -} - -/// Actual Calculation function for factoral -#[inline] -fn _mem_factorial(n: usize, table: &mut [u128]) -> Option { - if n > 34 { - return None - } - - match table[n] { - // Empty spaces in the lookup array are zero-filled - 0 => { - // The ? suffix passes along the Option value - // to be handled later - // See: https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator - let prev = _mem_factorial(n - 1, table)?; - - table[n] = (n as u128) * prev; - - Some(table[n]) - }, - x => Some(x), + Some(total) + } } } -/// Calculate the value of a factorial, +/// Calculate the value of a factorial recrursively /// /// Can calculate up to 34! using native unsigned 128 bit integers. /// @@ -184,36 +170,71 @@ mod tests { #[test] fn test_factorial() { - assert_eq!(1, factorial(0).unwrap()); - assert_eq!(1, factorial(1).unwrap()); - assert_eq!(6, factorial(3).unwrap()); + 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 = mem_factorial(34); + let res2 = it_factorial(34); assert!(res.is_some()); - assert_eq!(res.unwrap(), res2.unwrap()); + assert!(res2.is_some()); + assert_eq!(res, res2); - let res = factorial(35); - assert!(res.is_none()); + // Bounds checks + assert!(factorial(35).is_none()); + assert!(it_factorial(35).is_none()); } #[test] fn test_fibonacci() { - assert_eq!(0, fibonacci(0).unwrap()); - assert_eq!(1, fibonacci(1).unwrap()); - assert_eq!(1, fibonacci(2).unwrap()); - - let res = fibonacci(20); - let res2 = rec_fibonacci(20); - assert_eq!(res, res2); + // 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.unwrap(), res2.unwrap()); + assert_eq!(res, res2); - let res = fibonacci(187); - assert!(res.is_none()); + // Bounds checks + assert!(fibonacci(187).is_none()); + assert!(mem_fibonacci(187).is_none()); } }