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}