use std::{
cmp::{max, min},
iter::from_fn,
mem::{replace, take},
};
use num_bigint::BigUint;
use num_traits::{One, Zero};
use rayon::{current_num_threads, prelude::*};
type FibPair = (BigUint, BigUint);
pub struct Fib;
impl Fib {
#[must_use]
pub fn single(n: u128) -> BigUint {
match n {
0 => BigUint::zero(),
1 => BigUint::one(),
_ => Self::fib_fast_doubling_helper(n).0,
}
}
#[allow(clippy::similar_names)] fn fib_fast_doubling_helper(n: u128) -> FibPair {
if n == 0 {
return (BigUint::zero(), BigUint::one());
}
let (fk, fk1) = Self::fib_fast_doubling_helper(n / 2);
let two_fk1 = &fk1 << 1; let term = &two_fk1 - &fk;
let f2k = &fk * &term; let f2k1 = &fk * &fk + &fk1 * &fk1;
if n.is_multiple_of(2) {
(f2k, f2k1)
} else {
let f2k2 = &f2k1 + &f2k;
(f2k1, f2k2)
}
}
#[must_use]
pub fn range(start: u128, end: u128) -> Vec<BigUint> {
if end < start {
return Vec::new();
}
let total_count = (end - start + 1) as usize;
let num_threads = current_num_threads();
let chunk_size = max(1, total_count / num_threads);
let num_chunks = total_count.div_ceil(chunk_size);
let chunks: Vec<_> = (0..num_chunks)
.map(|i| {
let chunk_start = start + (i as u128) * (chunk_size as u128);
let chunk_end = min(chunk_start + (chunk_size as u128) - 1, end);
(chunk_start, chunk_end)
})
.collect();
chunks
.par_iter()
.flat_map_iter(|&(chunk_start, chunk_end)| {
let chunk_size = (chunk_end - chunk_start + 1) as usize;
let (mut a, mut b) = Self::fib_fast_doubling_helper(chunk_start);
let mut remaining = chunk_size;
from_fn(move || {
if remaining == 0 {
return None;
}
let next = &a + &b;
let out = take(&mut a);
a = replace(&mut b, next);
remaining -= 1;
Some(out)
})
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn correct_fib_formula() {
assert_eq!(Fib::single(0), BigUint::zero());
assert_eq!(Fib::single(1), BigUint::one());
assert_eq!(Fib::single(10), BigUint::from_str("55").unwrap());
assert_eq!(Fib::single(20), BigUint::from_str("6765").unwrap());
assert_eq!(
Fib::single(187),
BigUint::from_str("538522340430300790495419781092981030533").unwrap()
);
assert_eq!(
Fib::single(256),
BigUint::from_str("141693817714056513234709965875411919657707794958199867").unwrap()
);
}
#[test]
fn correct_sequence_generation() {
let fib_seq_1 = Fib::range(0, 100);
assert_eq!(fib_seq_1.len(), 101);
assert_eq!(fib_seq_1[0], BigUint::zero());
assert_eq!(fib_seq_1[1], BigUint::one());
assert_eq!(fib_seq_1[10], BigUint::from_str("55").unwrap());
assert_eq!(fib_seq_1[20], BigUint::from_str("6765").unwrap());
let fib_seq_2 = Fib::range(100, 300);
assert_eq!(fib_seq_2.len(), 201);
assert_eq!(
fib_seq_2[0],
BigUint::from_str("354224848179261915075").unwrap()
); assert_eq!(
fib_seq_2[1],
BigUint::from_str("573147844013817084101").unwrap()
); assert_eq!(
fib_seq_2[156],
BigUint::from_str("141693817714056513234709965875411919657707794958199867").unwrap()
);
}
#[test]
fn ordering_check() {
let fib_seq = Fib::range(0, 100);
for i in 3..fib_seq.len() {
assert!(fib_seq[i] > fib_seq[i - 1]);
}
}
#[test]
fn loop_over_fibonacci() {
for i in 0..=1000 {
let _ = Fib::single(i);
}
}
#[test]
fn loop_over_fibonacci_ranges() {
let ranges = vec![
(0, 100),
(50, 150),
(100, 200),
(150, 250),
(200, 300),
(350, 450),
(400, 500),
(450, 550),
(500, 600),
(550, 650),
(600, 700),
(650, 750),
(700, 800),
(750, 850),
(800, 900),
(850, 950),
(900, 1000),
];
for (start, end) in ranges {
let _ = Fib::range(start, end);
}
}
}