First commit
This commit is contained in:
commit
5e0844006e
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
Cargo.lock
|
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "rusty-fib-facts"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Timothy J Warren <tim@timshomepage.net>"]
|
||||||
|
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
|
109
benches/stock_functions.rs
Normal file
109
benches/stock_functions.rs
Normal file
@ -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);
|
405
src/lib.rs
Normal file
405
src/lib.rs
Normal file
@ -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<u128> {
|
||||||
|
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<u128> {
|
||||||
|
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<u128> {
|
||||||
|
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<u128> {
|
||||||
|
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<u128> {
|
||||||
|
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<u128> {
|
||||||
|
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<f64> {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user