Skip to main content

use_series/
progression.rs

1use crate::error::SeriesError;
2
3/// Returns the zero-based `index`th term of an arithmetic progression.
4///
5/// # Errors
6///
7/// Returns [`SeriesError::Overflow`] when the resulting term does not fit in
8/// `i128`.
9///
10/// # Examples
11///
12/// ```rust
13/// use use_series::arithmetic_nth_term;
14///
15/// assert_eq!(arithmetic_nth_term(3, 2, 4)?, 11);
16/// # Ok::<(), use_series::SeriesError>(())
17/// ```
18pub const fn arithmetic_nth_term(first: i128, step: i128, index: u64) -> Result<i128, SeriesError> {
19    let Some(offset) = step.checked_mul(index as i128) else {
20        return Err(SeriesError::Overflow {
21            operation: "arithmetic_nth_term",
22        });
23    };
24
25    match first.checked_add(offset) {
26        Some(value) => Ok(value),
27        None => Err(SeriesError::Overflow {
28            operation: "arithmetic_nth_term",
29        }),
30    }
31}
32
33/// Returns the sum of the first `terms` values of an arithmetic progression.
34///
35/// # Errors
36///
37/// Returns [`SeriesError::Overflow`] when the partial sum does not fit in
38/// `i128`.
39///
40/// # Examples
41///
42/// ```rust
43/// use use_series::arithmetic_sum;
44///
45/// assert_eq!(arithmetic_sum(3, 2, 5)?, 35);
46/// # Ok::<(), use_series::SeriesError>(())
47/// ```
48pub const fn arithmetic_sum(first: i128, step: i128, terms: u64) -> Result<i128, SeriesError> {
49    let mut sum = 0_i128;
50    let mut index = 0_u64;
51
52    while index < terms {
53        let Ok(term) = arithmetic_nth_term(first, step, index) else {
54            return Err(SeriesError::Overflow {
55                operation: "arithmetic_sum",
56            });
57        };
58
59        sum = match sum.checked_add(term) {
60            Some(value) => value,
61            None => {
62                return Err(SeriesError::Overflow {
63                    operation: "arithmetic_sum",
64                });
65            },
66        };
67
68        index += 1;
69    }
70
71    Ok(sum)
72}
73
74/// Returns the zero-based `index`th term of a geometric progression.
75///
76/// # Errors
77///
78/// Returns [`SeriesError::Overflow`] when the resulting term does not fit in
79/// `i128`.
80///
81/// # Examples
82///
83/// ```rust
84/// use use_series::geometric_nth_term;
85///
86/// assert_eq!(geometric_nth_term(2, 3, 4)?, 162);
87/// # Ok::<(), use_series::SeriesError>(())
88/// ```
89pub const fn geometric_nth_term(first: i128, ratio: i128, index: u64) -> Result<i128, SeriesError> {
90    let mut value = first;
91    let mut exponent = 0_u64;
92
93    while exponent < index {
94        value = match value.checked_mul(ratio) {
95            Some(next) => next,
96            None => {
97                return Err(SeriesError::Overflow {
98                    operation: "geometric_nth_term",
99                });
100            },
101        };
102
103        exponent += 1;
104    }
105
106    Ok(value)
107}
108
109/// Returns the sum of the first `terms` values of a geometric progression.
110///
111/// # Errors
112///
113/// Returns [`SeriesError::Overflow`] when the partial sum does not fit in
114/// `i128`.
115///
116/// # Examples
117///
118/// ```rust
119/// use use_series::geometric_sum;
120///
121/// assert_eq!(geometric_sum(2, 3, 4)?, 80);
122/// # Ok::<(), use_series::SeriesError>(())
123/// ```
124pub const fn geometric_sum(first: i128, ratio: i128, terms: u64) -> Result<i128, SeriesError> {
125    let mut sum = 0_i128;
126    let mut term = first;
127    let mut index = 0_u64;
128
129    while index < terms {
130        sum = match sum.checked_add(term) {
131            Some(value) => value,
132            None => {
133                return Err(SeriesError::Overflow {
134                    operation: "geometric_sum",
135                });
136            },
137        };
138
139        term = match term.checked_mul(ratio) {
140            Some(next) => next,
141            None if index + 1 == terms => term,
142            None => {
143                return Err(SeriesError::Overflow {
144                    operation: "geometric_sum",
145                });
146            },
147        };
148
149        index += 1;
150    }
151
152    Ok(sum)
153}
154
155#[cfg(test)]
156mod tests {
157    use super::{arithmetic_nth_term, arithmetic_sum, geometric_nth_term, geometric_sum};
158    use crate::SeriesError;
159
160    #[test]
161    fn computes_arithmetic_progressions() {
162        assert_eq!(arithmetic_nth_term(3, 2, 4), Ok(11));
163        assert_eq!(arithmetic_sum(3, 2, 5), Ok(35));
164        assert_eq!(arithmetic_sum(10, -3, 4), Ok(22));
165        assert_eq!(arithmetic_sum(8, 5, 0), Ok(0));
166    }
167
168    #[test]
169    fn computes_geometric_progressions() {
170        assert_eq!(geometric_nth_term(2, 3, 4), Ok(162));
171        assert_eq!(geometric_sum(2, 3, 4), Ok(80));
172        assert_eq!(geometric_sum(5, 1, 4), Ok(20));
173        assert_eq!(geometric_sum(4, -2, 4), Ok(-20));
174        assert_eq!(geometric_sum(7, 3, 0), Ok(0));
175    }
176
177    #[test]
178    fn reports_overflow() {
179        assert!(matches!(
180            arithmetic_nth_term(i128::MAX, 1, 1),
181            Err(SeriesError::Overflow {
182                operation: "arithmetic_nth_term"
183            })
184        ));
185        assert!(matches!(
186            arithmetic_sum(i128::MAX, 1, 2),
187            Err(SeriesError::Overflow {
188                operation: "arithmetic_sum"
189            })
190        ));
191        assert!(matches!(
192            geometric_nth_term(i128::MAX, 2, 1),
193            Err(SeriesError::Overflow {
194                operation: "geometric_nth_term"
195            })
196        ));
197        assert!(matches!(
198            geometric_sum(i128::MAX, 2, 2),
199            Err(SeriesError::Overflow {
200                operation: "geometric_sum"
201            })
202        ));
203    }
204}