fibonacci_like/
lib.rs

1#![warn(missing_docs)]
2#![cfg_attr(not(feature = "std"), no_std)]
3#![doc = include_str!("../README.md")]
4
5/// An error for when the given input could not be found in the sequence
6#[derive(Debug)]
7pub enum FindError {
8    /// The input was not found in the sequence
9    NotFound(Number),
10}
11
12impl core::fmt::Display for FindError {
13    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
14        match self {
15            FindError::NotFound(number) => {
16                write!(f, "The number `{}` was not found in the sequence", number)
17            }
18        }
19    }
20}
21
22#[cfg(feature = "std")]
23impl std::error::Error for FindError {}
24
25type FindResult<T> = Result<T, FindError>;
26
27/// The into_number trait
28///
29/// See [`Number`] for more information.
30pub trait IntoNumber {
31    /// Converts the given value into a [`Number`] type
32    fn into_number(self) -> Number;
33}
34
35/// The number return type
36///
37/// Will be either [`num_bigint::BigInt`] or [`i128`] based on whether the `big-int` feature is enabled or not
38#[cfg(feature = "big-int")]
39pub type Number = num_bigint::BigInt;
40
41/// The number return type
42///
43/// Will be either [`num_bigint::BigInt`] or [`i128`] based on whether the `big-int` feature is enabled or not
44#[cfg(not(feature = "big-int"))]
45pub type Number = i128;
46
47impl IntoNumber for i128 {
48    fn into_number(self) -> Number {
49        cfg_if::cfg_if! {
50            if #[cfg(feature = "big-int")] {
51                use num_bigint::ToBigInt;
52
53                self.to_bigint().unwrap()
54            } else {
55                self
56            }
57        }
58    }
59}
60
61fn update_array(numbers: &mut [Number; 2]) {
62    let old_x = &numbers[0];
63    let old_y = &numbers[1];
64    let new_y = old_x + old_y;
65    // Removes the need to clone if we add 0
66    numbers[0] = old_y + 0;
67    numbers[1] = new_y;
68}
69
70/// A sequence, represented by the two starting values, which are later used to compute further values
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct Sequence(pub Number, pub Number);
73
74impl Sequence {
75    /// Creates a new sequence with the given starting values
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// # use fibonacci_like::Sequence;
81    /// Sequence::new([1,1]);
82    /// ```
83    pub fn new(sequence: impl Into<Self>) -> Self {
84        sequence.into()
85    }
86
87    /// Returns the fibonacci sequence, represented as a [`Sequence`] struct
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// # use fibonacci_like::Sequence;
93    /// let sequence = Sequence::new([1, 1]);
94    /// let fib_sequence = Sequence::fibonacci();
95    ///
96    /// assert_eq!(sequence, fib_sequence);
97    /// ```
98    pub fn fibonacci() -> Self {
99        Self::new([1, 1])
100    }
101
102    /// Calculate the nth term of the sequence
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// # use fibonacci_like::{Sequence, IntoNumber};
108    /// let sequence = Sequence::fibonacci();
109    /// let nth_term = sequence.calculate(3);
110    ///
111    /// assert_eq!(nth_term, 2_i128.into_number());
112    /// ```
113    pub fn calculate(self, n: usize) -> Number {
114        let mut numbers = [self.0, self.1];
115
116        for _ in 2..n {
117            update_array(&mut numbers);
118        }
119
120        cfg_if::cfg_if! {
121            if #[cfg(feature = "big-int")] {
122                use num_bigint::ToBigInt;
123                numbers[1].to_bigint().unwrap()
124            } else {
125                numbers[1]
126            }
127        }
128    }
129
130    /// Find the given number's position in the sequence
131    ///
132    /// # Examples
133    ///
134    /// ```
135    /// # use fibonacci_like::{Sequence, IntoNumber};
136    /// let fifteenth = 610.into_number();
137    ///
138    /// let fib = Sequence::fibonacci();
139    ///
140    /// assert_eq!(fib.find(fifteenth).unwrap(), 15);
141    /// ```
142    pub fn find(self, number: Number) -> FindResult<usize> {
143        let mut numbers = [self.0, self.1];
144
145        if number == numbers[0] {
146            return Ok(1);
147        } else if number == numbers[1] {
148            return Ok(2);
149        }
150
151        let mut n = 2;
152        loop {
153            update_array(&mut numbers);
154            n += 1;
155
156            if numbers[1] > number {
157                break Err(FindError::NotFound(number));
158            }
159
160            if numbers[1] == number {
161                break Ok(n);
162            }
163        }
164    }
165}
166
167impl From<[i128; 2]> for Sequence {
168    fn from(array: [i128; 2]) -> Sequence {
169        Sequence(array[0].into_number(), array[1].into_number())
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    cfg_if::cfg_if! {
176        if #[cfg(feature = "big-int")] {
177            use num_bigint::BigInt;
178            use std::str::FromStr;
179        }
180    }
181
182    use super::*;
183
184    // This test does not work without big-int as the literal is too large to fit in i128
185    #[cfg(feature = "big-int")]
186    #[test]
187    fn test_get_500th() {
188        let nth500 = BigInt::from_str("139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125").unwrap();
189
190        let fib = Sequence::fibonacci();
191
192        assert_eq!(fib.calculate(500), nth500);
193    }
194
195    #[test]
196    fn test_get_first() {
197        let first = 1.into_number();
198
199        let fib = Sequence::fibonacci();
200
201        assert_eq!(fib.calculate(1), first);
202    }
203
204    #[test]
205    fn test_get_third() {
206        let third = 2.into_number();
207
208        let fib = Sequence::fibonacci();
209
210        assert_eq!(fib.calculate(3), third);
211    }
212
213    #[test]
214    fn test_find_first() {
215        let first = 1.into_number();
216
217        let fib = Sequence::fibonacci();
218
219        assert_eq!(fib.find(first).unwrap(), 1);
220    }
221
222    #[test]
223    fn test_find_third() {
224        let third = 2.into_number();
225
226        let fib = Sequence::fibonacci();
227
228        assert_eq!(fib.find(third).unwrap(), 3);
229    }
230
231    #[test]
232    fn test_find_fifteenth() {
233        let fifteenth = 610.into_number();
234
235        let fib = Sequence::fibonacci();
236
237        assert_eq!(fib.find(fifteenth).unwrap(), 15);
238    }
239}