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 mem::replace,
53};
54
55use num_bigint::BigUint;
56use num_traits::{One, Zero};
57use rayon::{current_num_threads, prelude::*};
58
59/// Type alias for the result of the fast doubling algorithm
60///
61/// Represents a pair of consecutive Fibonacci numbers (F(n), F(n+1))
62type FibPair = (BigUint, BigUint);
63
64/// A utility struct for computing Fibonacci numbers efficiently.
65///
66/// This struct provides static methods for calculating Fibonacci numbers using
67/// highly optimized algorithms. It supports both single Fibonacci number calculation
68/// and generating ranges of consecutive Fibonacci numbers.
69///
70/// The implementation uses a fast doubling algorithm for O(log n) time complexity
71/// and leverages parallel processing for range calculations to maximize performance.
72///
73/// For Fibonacci numbers beyond F(185), the implementation automatically switches to
74/// using `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 pub fn single(n: u128) -> BigUint {
109 match n {
110 0 => BigUint::zero(),
111 1 => BigUint::one(),
112 _ => Self::fib_fast_doubling_helper(n).0,
113 }
114 }
115
116 /// Helper function for the fast doubling algorithm.
117 ///
118 /// This function implements the recursive divide-and-conquer approach for computing
119 /// Fibonacci numbers using the fast doubling method. It returns a pair of consecutive
120 /// Fibonacci numbers (F(n), F(n+1)) to enable the recursion.
121 ///
122 /// The algorithm is based on the following mathematical identities:
123 /// - For even n: F(2k) = F(k) * (2*F(k+1) - F(k))
124 /// - For odd n: F(2k+1) = F(k+1)^2 + F(k)^2
125 ///
126 /// # Arguments
127 ///
128 /// * `n` - The index of the Fibonacci number to calculate
129 ///
130 /// # Returns
131 ///
132 /// * A tuple (F(n), F(n+1)) containing the nth and (n+1)th Fibonacci numbers
133 ///
134 /// # Time Complexity
135 ///
136 /// * O(log n) due to the recursive divide-and-conquer approach
137 fn fib_fast_doubling_helper(n: u128) -> FibPair {
138 if n == 0 {
139 return (BigUint::zero(), BigUint::one());
140 }
141
142 // Calculate F(k) and F(k+1) where k = floor(n/2)
143 let (fk, fk1) = Self::fib_fast_doubling_helper(n / 2);
144
145 // Calculate F(2k) and F(2k+1)
146 let two_fk1 = &fk1 << 1; // Efficiently multiply by 2 using bit shift
147 let term = &two_fk1 - &fk;
148 let f2k = &fk * &term; // F(2k) = F(k) * (2*F(k+1) - F(k))
149 let f2k1 = &fk * &fk + &fk1 * &fk1; // F(2k+1) = F(k)^2 + F(k+1)^2
150
151 // Return appropriate pair based on whether n is even or odd
152 if n.is_multiple_of(2) {
153 (f2k, f2k1)
154 } else {
155 let f2k2 = &f2k1 + &f2k;
156 (f2k1, f2k2)
157 }
158 }
159
160 /// Generates Fibonacci numbers for indices in the given inclusive range.
161 ///
162 /// This method efficiently computes a sequence of consecutive Fibonacci numbers
163 /// using parallel processing for improved performance. It divides the requested range
164 /// into optimal chunks based on the available CPU threads, calculates each chunk in
165 /// parallel, and then combines the results.
166 ///
167 /// The implementation uses a hybrid approach that:
168 /// 1. Uses the fast doubling algorithm to efficiently find the starting values for each chunk
169 /// 2. Computes subsequent Fibonacci numbers iteratively within each chunk
170 /// 3. Processes chunks in parallel using the Rayon library
171 ///
172 /// # Arguments
173 ///
174 /// * `start` - The starting index of the range
175 /// * `end` - The ending index of the range (inclusive)
176 ///
177 /// # Returns
178 ///
179 /// * A `Vec<BigUint>` containing ordered Fibonacci numbers for indices in the specified inclusive range.
180 ///
181 /// # Complexity
182 ///
183 /// * Time complexity: Approximately O(n/t + log(start)) where n is the range size and t is the number of threads.
184 /// * Space complexity: O(n) for storing the Fibonacci numbers in a vector.
185 ///
186 /// # Performance
187 ///
188 /// Performance scales with the number of available CPU cores. For large ranges, the
189 /// parallelization provides significant speedup compared to sequential calculation.
190 ///
191 /// # Examples
192 ///
193 /// ```
194 /// use fib_rs::Fib;
195 /// use num_bigint::BigUint;
196 /// use num_traits::{Zero, One};
197 ///
198 /// // Generate Fibonacci numbers from index 3 to 10 (both 3 and 10 inclusive)
199 /// let fibs = Fib::range(3, 10);
200 ///
201 /// assert_eq!(fibs.len(), 8); // indices 3 to 10
202 /// assert_eq!(fibs[0], BigUint::from(2u32)); // F(3) = 2
203 /// assert_eq!(fibs[7], BigUint::from(55u32)); // F(10) = 55
204 /// ```
205 ///
206 /// For large ranges, the parallel implementation provides significant performance improvements:
207 ///
208 /// ```
209 /// use fib_rs::Fib;
210 ///
211 /// // Generate 10,000 consecutive Fibonacci numbers efficiently
212 /// let large_range = Fib::range(1000, 10999);
213 /// assert_eq!(large_range.len(), 10000);
214 /// ```
215 pub fn range(start: u128, end: u128) -> Vec<BigUint> {
216 // Validate input range
217 if end < start {
218 return Vec::new();
219 }
220
221 // Calculate total number of Fibonacci numbers to generate
222 let total_count = (end - start + 1) as usize;
223
224 // Determine optimal chunk size for parallelization based on available CPU threads
225 // This balances parallelism with the overhead of creating too many small chunks
226 let num_threads = current_num_threads();
227 let chunk_size = max(1, total_count / num_threads);
228
229 // Calculate number of chunks with ceiling division to ensure we cover the entire range
230 let num_chunks = total_count.div_ceil(chunk_size);
231
232 // Create chunk boundaries for parallel processing
233 // Each chunk represents a subrange of consecutive Fibonacci numbers
234 let chunks: Vec<_> = (0..num_chunks)
235 .map(|i| {
236 let chunk_start = start + (i as u128) * (chunk_size as u128);
237 let chunk_end = min(chunk_start + (chunk_size as u128) - 1, end);
238 (chunk_start, chunk_end)
239 })
240 .collect();
241
242 // Process each chunk in parallel using Rayon's parallel iterator
243 // Each thread calculates a portion of the Fibonacci sequence independently
244 let results: Vec<Vec<BigUint>> = chunks
245 .par_iter()
246 .map(|&(chunk_start, chunk_end)| {
247 let chunk_size = (chunk_end - chunk_start + 1) as usize;
248 let mut result = Vec::with_capacity(chunk_size);
249
250 // Use the fast doubling algorithm to efficiently find the starting values
251 // for this chunk. This is a key optimization for chunk initialization.
252 let (mut a, mut b) = Self::fib_fast_doubling_helper(chunk_start);
253
254 // Add the first value (F(chunk_start)) to the result
255 result.push(a.clone());
256
257 // Compute the rest of the chunk iteratively using the recurrence relation:
258 // F(n+2) = F(n+1) + F(n)
259 // This is more efficient than using the fast doubling algorithm for each number
260 for _ in 1..chunk_size {
261 let next = &a + &b;
262 a = replace(&mut b, next); // Efficiently swap values
263 result.push(a.clone());
264 }
265
266 result
267 })
268 .collect();
269
270 // Combine results from all chunks into a single, continuous vector
271 // Pre-allocate the exact size needed to avoid multiple reallocations
272 let mut result = Vec::with_capacity(total_count);
273 for chunk in results {
274 result.extend(chunk);
275 }
276
277 result
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284 use std::str::FromStr;
285
286 #[test]
287 fn correct_fib_formula() {
288 // The formula is respected
289 assert_eq!(Fib::single(0), BigUint::zero());
290 assert_eq!(Fib::single(1), BigUint::one());
291 assert_eq!(Fib::single(10), BigUint::from_str("55").unwrap());
292 assert_eq!(Fib::single(20), BigUint::from_str("6765").unwrap());
293 // Beyond u128 outputs are correct
294 assert_eq!(
295 Fib::single(187),
296 BigUint::from_str("538522340430300790495419781092981030533").unwrap()
297 );
298 // Beyond u8 inputs produces correct output
299 assert_eq!(
300 Fib::single(256),
301 BigUint::from_str("141693817714056513234709965875411919657707794958199867").unwrap()
302 );
303 }
304
305 #[test]
306 fn correct_sequence_generation() {
307 let fib_seq_1 = Fib::range(0, 100);
308 assert_eq!(fib_seq_1.len(), 101);
309 assert_eq!(fib_seq_1[0], BigUint::zero());
310 assert_eq!(fib_seq_1[1], BigUint::one());
311 assert_eq!(fib_seq_1[10], BigUint::from_str("55").unwrap());
312 assert_eq!(fib_seq_1[20], BigUint::from_str("6765").unwrap());
313
314 let fib_seq_2 = Fib::range(100, 300);
315 assert_eq!(fib_seq_2.len(), 201);
316 assert_eq!(
317 fib_seq_2[0],
318 BigUint::from_str("354224848179261915075").unwrap()
319 ); // Supposed to be fib 100
320 assert_eq!(
321 fib_seq_2[1],
322 BigUint::from_str("573147844013817084101").unwrap()
323 ); // Supposed to be fib 101
324 assert_eq!(
325 fib_seq_2[156],
326 BigUint::from_str("141693817714056513234709965875411919657707794958199867").unwrap()
327 );
328 }
329
330 #[test]
331 fn ordering_check() {
332 // Check that the Fibonacci sequence is strictly increasing
333 let fib_seq = Fib::range(0, 100);
334 // Start from the third element since the first three results are 0, 1, 1
335 for i in 3..fib_seq.len() {
336 assert!(fib_seq[i] > fib_seq[i - 1]);
337 }
338 }
339
340 // Loop over all the Fibonacci numbers to ensure the function does not panic over a variety of inputs
341 #[test]
342 fn loop_over_fibonacci() {
343 for i in 0..=1000 {
344 Fib::single(i);
345 }
346 }
347
348 // Test with various ranges
349 #[test]
350 fn loop_over_fibonacci_ranges() {
351 let ranges = vec![
352 (0, 100),
353 (50, 150),
354 (100, 200),
355 (150, 250),
356 (200, 300),
357 (350, 450),
358 (400, 500),
359 (450, 550),
360 (500, 600),
361 (550, 650),
362 (600, 700),
363 (650, 750),
364 (700, 800),
365 (750, 850),
366 (800, 900),
367 (850, 950),
368 (900, 1000),
369 ];
370
371 for (start, end) in ranges {
372 Fib::range(start, end);
373 }
374 }
375}