rusty-numbers/src/seq.rs

113 lines
3.0 KiB
Rust

//! Sequences and other 'stock' math functions
/// Calculate a number in the fibonacci sequence,
/// using a lookup table for better worst-case performance.
///
/// Can calculate up to 186 using native unsigned 128 bit integers.
///
/// Example
/// ```rust
/// use rusty_numbers::seq::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());
/// ```
pub fn fibonacci(n: usize) -> Option<u128> {
let mut table: Vec<u128> = vec![0, 1, 1, 2, 3, 5];
_fibonacci(n, &mut table)
}
/// Actual calculating function for `fibonacci`
#[inline]
fn _fibonacci(n: usize, table: &mut Vec<u128>) -> Option<u128> {
match table.get(n) {
Some(x) => Some(*x),
None => {
let a = _fibonacci(n - 1, table)?;
let b = _fibonacci(n - 2, table)?;
// Check for overflow when adding
let attempt = a.checked_add(b);
if let Some(current) = attempt {
table.insert(n, current);
}
attempt
}
}
}
/// Calculate the value of a factorial,
/// using a lookup table for better worst-case performance.
///
/// Can calculate up to 34! using native unsigned 128 bit integers.
pub fn factorial(n: usize) -> Option<u128> {
let mut table: Vec<u128> = vec![1, 1, 2];
_factorial(n, &mut table)
}
/// Actual Calculation function for factoral
#[inline]
fn _factorial(n: usize, table: &mut Vec<u128>) -> Option<u128> {
match table.get(n) {
// Vec<T>.get returns a Option with a reference to the value,
// so deref and wrap in Some() for proper return type
Some(x) => Some(*x),
None => {
// 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 = _factorial(n - 1, table)?;
// Do an overflow-checked multiply
let attempt = (n as u128).checked_mul(prev);
// If there isn't an overflow, add the result
// to the calculation table
if let Some(current) = attempt {
table.insert(n, current);
}
attempt // Some(x) if no overflow
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_factorial() {
assert_eq!(1, factorial(0).unwrap());
assert_eq!(1, factorial(1).unwrap());
assert_eq!(6, factorial(3).unwrap());
let res = factorial(34);
assert!(res.is_some());
let res = factorial(35);
assert!(res.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(186);
assert!(res.is_some());
let res = fibonacci(187);
assert!(res.is_none());
}
}