//! # 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)); } }