Skip to main content

fib_rs/
lib.rs

1//! # fib-rs
2//!
3//! A highly optimized Fibonacci number calculator for Rust that efficiently computes
4//! arbitrarily large Fibonacci numbers.
5//!
6//! ## Features
7//!
8//! - **Fast doubling algorithm**: Calculates Fibonacci numbers in O(log n) time
9//! - **Handles massive inputs**: Compute Fibonacci numbers up to F(10,000,000) and beyond
10//! - **Range calculation**: Generate sequences of consecutive Fibonacci numbers with parallel processing
11//! - **`BigUint` support**: Uses arbitrary precision integers for handling large Fibonacci numbers
12//!
13//! ## Examples
14//!
15//! ```
16//! use fib_rs::Fib;
17//! use num_bigint::BigUint;
18//! use num_traits::Zero;
19//!
20//! // Calculate a single Fibonacci number
21//! let n = 100;
22//! let result = Fib::single(n);
23//! assert!(result > BigUint::zero());
24//!
25//! // Calculate a range of Fibonacci numbers (F(3) through F(10))
26//! let start = 3;
27//! let end = 10;
28//! let results = Fib::range(start, end);
29//! assert_eq!(results.len(), (end - start + 1) as usize);
30//! ```
31//!
32//! ## Algorithm Details
33//!
34//! ### Single Fibonacci Number
35//!
36//! For computing a single Fibonacci number, this implementation uses the fast doubling algorithm
37//! with logarithmic time complexity:
38//!
39//! - For even n: F(2k) = F(k) * (2*F(k+1) - F(k))
40//! - For odd n:  F(2k+1) = F(k+1)^2 + F(k)^2
41//!
42//! ### Fibonacci Range
43//!
44//! The range implementation combines three approaches for optimal performance:
45//!
46//! 1. **Parallel processing**: Divides the requested range into optimal chunks based on available CPU threads
47//! 2. **Smart initialization**: Uses the fast doubling algorithm to efficiently find the starting values for each chunk
48//! 3. **Iterative calculation**: After finding starting values, computes subsequent Fibonacci numbers iteratively within each chunk
49
50use std::{
51    cmp::{max, min},
52    iter::from_fn,
53    mem::{replace, take},
54};
55
56use num_bigint::BigUint;
57use num_traits::{One, Zero};
58use rayon::{current_num_threads, prelude::*};
59
60/// Type alias for the result of the fast doubling algorithm
61///
62/// Represents a pair of consecutive Fibonacci numbers (F(n), F(n+1))
63type FibPair = (BigUint, BigUint);
64
65/// A utility struct for computing Fibonacci numbers efficiently.
66///
67/// This struct provides static methods for calculating Fibonacci numbers using
68/// highly optimized algorithms. It supports both single Fibonacci number calculation
69/// and generating ranges of consecutive Fibonacci numbers.
70///
71/// The implementation uses a fast doubling algorithm for O(log n) time complexity
72/// and leverages parallel processing for range calculations to maximize performance.
73///
74/// Uses `BigUint` for arbitrary precision, ensuring correct results for extremely large
75/// Fibonacci numbers.
76pub struct Fib;
77
78impl Fib {
79    /// Calculate the nth Fibonacci number using an optimized fast doubling algorithm.
80    ///
81    /// This method efficiently computes Fibonacci numbers of arbitrary size by using
82    /// a divide-and-conquer approach based on matrix identities.
83    ///
84    /// # Arguments
85    ///
86    /// * `n` - The index of the Fibonacci number to calculate (0-indexed, where F(0)=0, F(1)=1)
87    ///
88    /// # Returns
89    ///
90    /// * The nth Fibonacci number as a `BigUint`
91    ///
92    /// # Complexity
93    ///
94    /// * Time complexity: O(log n) due to the fast doubling algorithm
95    /// * Space complexity: O(log n) for the recursive call stack
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use fib_rs::Fib;
101    /// use num_bigint::BigUint;
102    /// use num_traits::Zero;
103    ///
104    /// assert_eq!(Fib::single(0), BigUint::zero()); // F(0) = 0
105    /// assert_eq!(Fib::single(10), BigUint::from(55u32)); // F(10) = 55
106    /// assert!(Fib::single(200) > BigUint::from(u128::MAX)); // Large value example (would overflow primitive types)
107    /// ```
108    #[must_use]
109    pub fn single(n: u128) -> BigUint {
110        match n {
111            0 => BigUint::zero(),
112            1 => BigUint::one(),
113            _ => Self::fib_fast_doubling_helper(n).0,
114        }
115    }
116
117    /// Helper function for the fast doubling algorithm.
118    ///
119    /// This function implements the recursive divide-and-conquer approach for computing
120    /// Fibonacci numbers using the fast doubling method. It returns a pair of consecutive
121    /// Fibonacci numbers (F(n), F(n+1)) to enable the recursion.
122    ///
123    /// The algorithm is based on the following mathematical identities:
124    /// - For even n: F(2k) = F(k) * (2*F(k+1) - F(k))
125    /// - For odd n:  F(2k+1) = F(k+1)^2 + F(k)^2
126    ///
127    /// # Arguments
128    ///
129    /// * `n` - The index of the Fibonacci number to calculate
130    ///
131    /// # Returns
132    ///
133    /// * A tuple (F(n), F(n+1)) containing the nth and (n+1)th Fibonacci numbers
134    ///
135    /// # Time Complexity
136    ///
137    /// * O(log n) due to the recursive divide-and-conquer approach
138    #[allow(clippy::similar_names)] // Mathematical notation: F(k), F(k+1), F(2k), F(2k+1)
139    fn fib_fast_doubling_helper(n: u128) -> FibPair {
140        if n == 0 {
141            return (BigUint::zero(), BigUint::one());
142        }
143
144        // Calculate F(k) and F(k+1) where k = floor(n/2)
145        let (fk, fk1) = Self::fib_fast_doubling_helper(n / 2);
146
147        // Calculate F(2k) and F(2k+1)
148        let two_fk1 = &fk1 << 1; // Efficiently multiply by 2 using bit shift
149        let term = &two_fk1 - &fk;
150        let f2k = &fk * &term; // F(2k) = F(k) * (2*F(k+1) - F(k))
151        let f2k1 = &fk * &fk + &fk1 * &fk1; // F(2k+1) = F(k)^2 + F(k+1)^2
152
153        // Return appropriate pair based on whether n is even or odd
154        // Already internally does a bitwise operation
155        if n.is_multiple_of(2) {
156            (f2k, f2k1)
157        } else {
158            let f2k2 = &f2k1 + &f2k;
159            (f2k1, f2k2)
160        }
161    }
162
163    /// Generates Fibonacci numbers for indices in the given inclusive range.
164    ///
165    /// This method efficiently computes a sequence of consecutive Fibonacci numbers
166    /// using parallel processing for improved performance. It divides the requested range
167    /// into optimal chunks based on the available CPU threads, calculates each chunk in
168    /// parallel, and then combines the results.
169    ///
170    /// The implementation uses a hybrid approach that:
171    /// 1. Uses the fast doubling algorithm to efficiently find the starting values for each chunk
172    /// 2. Computes subsequent Fibonacci numbers iteratively within each chunk
173    /// 3. Processes chunks in parallel using the Rayon library
174    ///
175    /// # Arguments
176    ///
177    /// * `start` - The starting index of the range
178    /// * `end` - The ending index of the range (inclusive)
179    ///
180    /// # Returns
181    ///
182    /// * A `Vec<BigUint>` containing ordered Fibonacci numbers for indices in the specified inclusive range.
183    ///
184    /// # Complexity
185    ///
186    /// * Time complexity: Approximately O(n/t + log(start)) where n is the range size and t is the number of threads.
187    /// * Space complexity: O(n) for storing the Fibonacci numbers in a vector.
188    ///
189    /// # Performance
190    ///
191    /// Performance scales with the number of available CPU cores. For large ranges, the
192    /// parallelization provides significant speedup compared to sequential calculation.
193    ///
194    /// # Examples
195    ///
196    /// ```
197    /// use fib_rs::Fib;
198    /// use num_bigint::BigUint;
199    /// use num_traits::{Zero, One};
200    ///
201    /// // Generate Fibonacci numbers from index 3 to 10 (both 3 and 10 inclusive)
202    /// let fibs = Fib::range(3, 10);
203    ///
204    /// assert_eq!(fibs.len(), 8); // indices 3 to 10
205    /// assert_eq!(fibs[0], BigUint::from(2u32)); // F(3) = 2
206    /// assert_eq!(fibs[7], BigUint::from(55u32)); // F(10) = 55
207    /// ```
208    ///
209    /// For large ranges, the parallel implementation provides significant performance improvements:
210    ///
211    /// ```
212    /// use fib_rs::Fib;
213    ///
214    /// // Generate 10,000 consecutive Fibonacci numbers efficiently
215    /// let large_range = Fib::range(1000, 10999);
216    /// assert_eq!(large_range.len(), 10000);
217    /// ```
218    #[must_use]
219    pub fn range(start: u128, end: u128) -> Vec<BigUint> {
220        // Validate input range
221        if end < start {
222            return Vec::new();
223        }
224
225        // Calculate total number of Fibonacci numbers to generate
226        let total_count = (end - start + 1) as usize;
227
228        // Determine optimal chunk size for parallelization based on available CPU threads
229        // This balances parallelism with the overhead of creating too many small chunks
230        let num_threads = current_num_threads();
231        let chunk_size = max(1, total_count / num_threads);
232
233        // Calculate number of chunks with ceiling division to ensure we cover the entire range
234        let num_chunks = total_count.div_ceil(chunk_size);
235
236        // Create chunk boundaries for parallel processing
237        // Each chunk represents a subrange of consecutive Fibonacci numbers
238        let chunks: Vec<_> = (0..num_chunks)
239            .map(|i| {
240                let chunk_start = start + (i as u128) * (chunk_size as u128);
241                let chunk_end = min(chunk_start + (chunk_size as u128) - 1, end);
242                (chunk_start, chunk_end)
243            })
244            .collect();
245
246        // Process each chunk in parallel using Rayon's parallel iterator
247        // Each thread calculates a portion of the Fibonacci sequence independently
248        chunks
249            .par_iter()
250            .flat_map_iter(|&(chunk_start, chunk_end)| {
251                let chunk_size = (chunk_end - chunk_start + 1) as usize;
252
253                // Use the fast doubling algorithm to efficiently find the starting values
254                // for this chunk. This is a key optimization for chunk initialization.
255                let (mut a, mut b) = Self::fib_fast_doubling_helper(chunk_start);
256                let mut remaining = chunk_size;
257
258                // Compute the chunk iteratively using the recurrence relation:
259                // F(n+2) = F(n+1) + F(n)
260                // This is more efficient than using the fast doubling algorithm for each number
261                from_fn(move || {
262                    if remaining == 0 {
263                        return None;
264                    }
265                    let next = &a + &b;
266                    let out = take(&mut a);
267                    a = replace(&mut b, next);
268                    remaining -= 1;
269                    Some(out)
270                })
271            })
272            .collect()
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use std::str::FromStr;
280
281    #[test]
282    fn correct_fib_formula() {
283        // The formula is respected
284        assert_eq!(Fib::single(0), BigUint::zero());
285        assert_eq!(Fib::single(1), BigUint::one());
286        assert_eq!(Fib::single(10), BigUint::from_str("55").unwrap());
287        assert_eq!(Fib::single(20), BigUint::from_str("6765").unwrap());
288        // Beyond u128 outputs are correct
289        assert_eq!(
290            Fib::single(187),
291            BigUint::from_str("538522340430300790495419781092981030533").unwrap()
292        );
293        // Beyond u8 inputs produces correct output
294        assert_eq!(
295            Fib::single(256),
296            BigUint::from_str("141693817714056513234709965875411919657707794958199867").unwrap()
297        );
298    }
299
300    #[test]
301    fn correct_sequence_generation() {
302        let fib_seq_1 = Fib::range(0, 100);
303        assert_eq!(fib_seq_1.len(), 101);
304        assert_eq!(fib_seq_1[0], BigUint::zero());
305        assert_eq!(fib_seq_1[1], BigUint::one());
306        assert_eq!(fib_seq_1[10], BigUint::from_str("55").unwrap());
307        assert_eq!(fib_seq_1[20], BigUint::from_str("6765").unwrap());
308
309        let fib_seq_2 = Fib::range(100, 300);
310        assert_eq!(fib_seq_2.len(), 201);
311        assert_eq!(
312            fib_seq_2[0],
313            BigUint::from_str("354224848179261915075").unwrap()
314        ); // Supposed to be fib 100
315        assert_eq!(
316            fib_seq_2[1],
317            BigUint::from_str("573147844013817084101").unwrap()
318        ); // Supposed to be fib 101
319        assert_eq!(
320            fib_seq_2[156],
321            BigUint::from_str("141693817714056513234709965875411919657707794958199867").unwrap()
322        );
323    }
324
325    #[test]
326    fn ordering_check() {
327        // Check that the Fibonacci sequence is strictly increasing
328        let fib_seq = Fib::range(0, 100);
329        // Start from the third element since the first three results are 0, 1, 1
330        for i in 3..fib_seq.len() {
331            assert!(fib_seq[i] > fib_seq[i - 1]);
332        }
333    }
334
335    // Loop over all the Fibonacci numbers to ensure the function does not panic over a variety of inputs
336    #[test]
337    fn loop_over_fibonacci() {
338        for i in 0..=1000 {
339            let _ = Fib::single(i);
340        }
341    }
342
343    // Test with various ranges
344    #[test]
345    fn loop_over_fibonacci_ranges() {
346        let ranges = vec![
347            (0, 100),
348            (50, 150),
349            (100, 200),
350            (150, 250),
351            (200, 300),
352            (350, 450),
353            (400, 500),
354            (450, 550),
355            (500, 600),
356            (550, 650),
357            (600, 700),
358            (650, 750),
359            (700, 800),
360            (750, 850),
361            (800, 900),
362            (850, 950),
363            (900, 1000),
364        ];
365
366        for (start, end) in ranges {
367            let _ = Fib::range(start, end);
368        }
369    }
370}