Skip to main content

horner_eval/
lib.rs

1//! # horner-eval
2//!
3//! A macro for evaluating polynomials via Horner's rule.
4
5use num_traits::MulAdd;
6
7/// Identical to `x.mul_add(a, b)`; used to generate expression nest without
8/// provoking ambiguities which would otherwise arise due to automatic dereferencing.
9///
10/// # Examples
11/// ```
12/// use horner_eval::muladd;
13///
14/// assert_eq!(7.0_f64.mul_add(2.0, 3.0), muladd(7.0, 2.0, 3.0))
15/// ```
16#[inline]
17pub fn muladd<T: MulAdd + MulAdd<Output = T>>(x: T, a: T, b: T) -> T {
18    x.mul_add(a, b)
19}
20// pub fn __zero<T: Zero + MulAdd + MulAdd<Output = T>>(x: T) -> T {
21//     T::zero()
22// }
23
24/// Evaluate the polynomial `a₀ + a₁x + ⋯ + aₙ₋₁xⁿ⁻¹ + aⁿxⁿ` via Horner's rule.
25/// This macro unrolls what would otherwise be a loop into the
26/// expression `(⋯(aₙx + aₙ₋₁)x + ⋯ + a₁)x + a₀`.
27///
28/// # Examples
29/// ```
30/// use horner_eval::horner;
31///
32/// let x = 2.0_f64;
33///
34/// let (a0, a1, a2) = (1.0, 2.0, 3.0);
35///
36/// // Coefficients are given in ascending order by power of `x`.
37/// assert_eq!(17.0, horner!(x, a0, a1, a2));
38///
39/// // Arbitrary expressions are permitted for the coefficients.
40/// assert_eq!(53.5, horner!(x + 5.0, x - 1.0, 2.0 * x, x / 4.0));
41///
42/// // Works on any type which implements `num_traits::MulAdd`
43/// assert_eq!(79, horner!(2, 1, 3, 0, 5, 0, 1));
44/// ```
45#[macro_export]
46macro_rules! horner {
47    // ( $x:tt, $a0:tt, $a1:tt ) => {
48    //     $crate::muladd($a1, $x, $a0)
49    // };
50    // ( $x:tt, $a0:tt, $( $a1:tt ),+ ) => {
51    //     $crate::muladd( $crate::horner!( $x, $( $a1 ),+ ), $x, $a0 )
52    // };
53    // ( $x:tt, $a0:tt ) => { $a0 }
54    // expr? more permissible...
55    ( $x:expr, $a0:expr, $a1:expr ) => {
56        $crate::muladd($a1, $x, $a0)
57    };
58    ( $x:expr, $a0:expr, $( $a1:expr ),+ ) => {
59        $crate::muladd( $crate::horner!( $x, $( $a1 ),+ ), $x, $a0 )
60    };
61    ( $x:expr, $a0:expr ) => { $a0 }
62    // ( $x:expr ) => { __zero($x) }
63}
64
65/// Evaluate the polynomial `a₀ + a₁x + ⋯ + aₙ₋₁xⁿ⁻¹ + aⁿxⁿ` via Horner's rule.
66/// As the name indicates, this function uses an explicit loop
67/// to accommodate dynamically-sized coefficient slices.
68///
69/// # Examples
70/// ```
71/// use horner_eval::horner_loop;
72///
73/// let x = 2.0_f64;
74///
75/// let c: Vec<f64> = vec![1.0, 2.0, 3.0];
76///
77/// assert_eq!(17.0, horner_loop(x, &c));
78/// ```
79pub fn horner_loop<T>(x: T, coefficients: &[T]) -> T
80where
81    T: Copy + MulAdd + MulAdd<Output = T>,
82{
83    let n = coefficients.len();
84    if n > 0 {
85        let a_n = coefficients[n - 1];
86        coefficients[0..n - 1]
87            .iter()
88            .rfold(a_n, |result, &a| result.mul_add(x, a))
89    } else {
90        panic!("coefficients.len() must be greater than or equal to 1, got {}", n);
91    }
92}
93
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn internal_muladd_integer() {
101        macro_rules! test_muladd {
102            ($($t:ident)+) => {
103                $(
104                    {
105                        let x: $t = 2;
106                        let a: $t = 19;
107                        let b: $t = 4;
108
109                        assert_eq!(muladd(x, a, b), (x * a + b));
110                    }
111                )+
112            };
113        }
114
115        test_muladd!(usize u8 u16 u32 u64 isize i8 i16 i32 i64);
116    }
117
118    #[test]
119    fn internal_muladd_float() {
120        macro_rules! test_muladd {
121            ($($t:ident)+) => {
122                $(
123                    {
124                        use core::$t;
125
126                        let x: $t = 12.0;
127                        let a: $t = 3.4;
128                        let b: $t = 5.6;
129
130                        let abs_difference = (muladd(x, a, b) - (x * a + b)).abs();
131
132                        assert!(abs_difference <= 46.4 * $t::EPSILON);
133                    }
134                )+
135            };
136        }
137
138        test_muladd!(f32 f64);
139    }
140
141    #[test]
142    fn horner_integer() {
143        macro_rules! test_horner_integer {
144            ($($t:ident)+) => {
145                $(
146                    {
147                        let x: $t = 2;
148                        let a0: $t = 1;
149                        let a1: $t = 2;
150                        let a2: $t = 3;
151
152                        assert_eq!(17, horner!(x, a0, a1, a2));
153
154                        assert_eq!(101, horner!(x, a0, a1, a1, a2, a1, a0));
155                    }
156                )+
157            };
158        }
159
160        test_horner_integer!(usize u8 u16 u32 u64 isize i8 i16 i32 i64);
161    }
162
163    #[test]
164    fn horner_float() {
165        macro_rules! test_horner_float {
166            ($($t:ident)+) => {
167                $(
168                    {
169                        let x: $t = 2.0;
170                        let a0: $t = 1.0;
171                        let a1: $t = 2.0;
172                        let a2: $t = 3.0;
173
174                        assert_eq!(17.0, horner!(x, a0, a1, a2));
175
176                        assert_eq!(101.0, horner!(x, a0, a1, a1, a2, a1, a0));
177
178                        let y: $t = 5.5;
179                        let abs_difference = 1.1985066439401153 - horner!(y, 7.72156649015328655494e-02, 6.73523010531292681824e-02, 7.38555086081402883957e-03, 1.19270763183362067845e-03, 2.20862790713908385557e-04, 2.52144565451257326939e-05);
180                        assert!(abs_difference <= $t::EPSILON);
181
182                        let a3: $t = 4.0;
183                        let a4: $t = 5.0;
184                        let a5: $t = 6.0;
185                        let a6: $t = 7.0;
186                        let a7: $t = 8.0;
187                        let a8: $t = 9.0;
188
189                        assert_eq!(4097.0, horner!(x, a0, a1, a2, a3, a4, a5, a6, a7, a8));
190                    }
191                )+
192            };
193        }
194
195        test_horner_float!(f32 f64);
196    }
197
198    #[test]
199    fn horner_loop_integer() {
200        macro_rules! test_horner_loop_integer {
201            ($($t:ident)+) => {
202                $(
203                    {
204                        let x: $t = 2;
205                        let c: Vec<$t> = vec![1, 2, 3];
206                        let c1: Vec<$t> = vec![1];
207                        assert_eq!(17, horner_loop(x, &c));
208                        assert_eq!(1, horner_loop(x, &c1));
209                    }
210                )+
211            }
212        }
213
214        test_horner_loop_integer!(usize u8 u16 u32 u64 isize i8 i16 i32 i64);
215    }
216
217    #[test]
218    fn horner_loop_float() {
219        macro_rules! test_horner_loop_float {
220            ($($t:ident)+) => {
221                $(
222                    {
223                        let x: $t = 2.0;
224                        let c: Vec<$t> = vec![1.0, 2.0, 3.0];
225                        let c1: Vec<$t> = vec![1.0];
226                        assert_eq!(17.0, horner_loop(x, &c));
227                        assert_eq!(1.0, horner_loop(x, &c1));
228                    }
229                )+
230            }
231        }
232
233        test_horner_loop_float!(f32 f64);
234    }
235
236    #[test]
237    #[should_panic(expected = "coefficients.len() must be greater than")]
238    fn horner_loop_empty_vec() {
239        let x = 2.0;
240        let c: Vec<f64> = vec![];
241        horner_loop(x, &c);
242    }
243
244}