Skip to main content

malachite_nz/integer_polynomial/arithmetic/
evaluate.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::integer::Integer;
10use crate::integer_polynomial::IntegerPolynomial;
11use crate::natural::Natural;
12use crate::platform::Limb;
13use alloc::vec::Vec;
14use core::iter::Sum;
15use core::mem::replace;
16use core::ops::{AddAssign, Mul, MulAssign};
17use malachite_base::num::arithmetic::traits::{Parity, PowerOf2, Square};
18use malachite_base::num::basic::integers::PrimitiveInt;
19use malachite_base::num::basic::traits::{One, Zero};
20use malachite_base::num::conversion::traits::ExactFrom;
21use malachite_base::num::logic::traits::{BitAccess, SignificantBits};
22use malachite_base::polynomial::{Evaluate, EvaluateMany, ModEvaluate};
23use malachite_base::unsigned_polynomial::arithmetic::evaluate::mod_evaluate_slice;
24
25// Evaluates a polynomial, given by its coefficients in ascending order, at `x` with Horner's rule.
26//
27// This is equivalent to `_fmpz_poly_evaluate_horner_fmpz` from `fmpz_poly/evaluate_horner_fmpz.c`,
28// FLINT 3.6.0.
29crate_test_fn! {evaluate_horner<T>(coefficients: &[T], x: &T) -> T
30where
31    T: Clone + Zero + PartialEq + for<'a> AddAssign<&'a T> + for<'a> MulAssign<&'a T>,
32{
33    let Some((leading, rest)) = coefficients.split_last() else {
34        return T::ZERO;
35    };
36    if rest.is_empty() || *x == T::ZERO {
37        return coefficients[0].clone();
38    }
39    let mut value = leading.clone();
40    for c in rest.iter().rev() {
41        value *= x;
42        value += c;
43    }
44    value
45}}
46
47// The block structure shared by the divide-and-conquer evaluations, at an integer here and at a
48// rational in malachite-q.
49//
50// Adjacent coefficients are paired into blocks, and blocks are merged like the carries of a binary
51// counter: a block covering `2^k` coefficients waits in `partials[k]` until an equal one follows
52// it, and the two are merged. Merging equal halves keeps the operands of each multiplication about
53// the same size, where Horner's rule multiplies an ever larger accumulator by the same value.
54// Whatever blocks remain at the end are merged from the smallest up.
55//
56// `pair(c_i, c_{i+1})` makes the block of two adjacent coefficients, and `single(c)` the block of
57// the last coefficient when the length is odd. `merge(lower, upper, k, upper_len)` combines a block
58// `lower` of `2^k` coefficients with the block `upper` of `upper_len` coefficients directly above
59// it. There must be at least 2 coefficients.
60//
61// This is the loop structure of `_fmpz_poly_evaluate_divconquer_fmpz` from
62// `fmpz_poly/evaluate_divconquer_fmpz.c`, FLINT 3.6.0, with the arithmetic left to the caller.
63#[doc(hidden)]
64pub fn divide_and_conquer_blocks<C, T: Clone + Zero>(
65    coefficients: &[C],
66    pair: impl Fn(&C, &C) -> T,
67    single: impl Fn(&C) -> T,
68    merge: impl Fn(&T, T, usize, usize) -> T,
69) -> T {
70    let len = coefficients.len();
71    assert!(len >= 2);
72    // 2^{h - 1} < len <= 2^h, and h >= 1.
73    let h = usize::exact_from((len - 1).significant_bits());
74    let mut partials = vec![T::ZERO; h + 1];
75    // Absorbs a block of `block_len` coefficients ending just before coefficient `end` into the
76    // pending blocks, merging it with as many of them as the carries of `end` call for, and returns
77    // where it was left and how many coefficients it then covers.
78    let absorb =
79        |mut block: T, mut block_len: usize, end: usize, partials: &mut [T]| -> (usize, usize) {
80            let carries = usize::exact_from(end.trailing_zeros());
81            let mut k = 1;
82            while k < carries {
83                block = merge(&partials[k], block, k, block_len);
84                block_len += usize::power_of_2(u64::exact_from(k));
85                k += 1;
86            }
87            partials[k] = block;
88            (k, block_len)
89        };
90    let mut k = 1;
91    let mut top_len = 0;
92    for (i, [low, high]) in coefficients.as_chunks::<2>().0.iter().enumerate() {
93        (k, top_len) = absorb(pair(low, high), 2, (i + 1) << 1, &mut partials);
94    }
95    if len.odd() {
96        (k, top_len) = absorb(single(&coefficients[len - 1]), 1, len + 1, &mut partials);
97    }
98    let mut value = replace(&mut partials[k], T::ZERO);
99    while k < h {
100        if (len - 1).get_bit(u64::exact_from(k)) {
101            value = merge(&partials[k], value, k, top_len);
102            top_len += usize::power_of_2(u64::exact_from(k));
103        }
104        k += 1;
105    }
106    value
107}
108
109// Evaluates a polynomial, given by its coefficients in ascending order, at `x` by divide and
110// conquer: a block of coefficients `c_i, ..., c_{i + l - 1}` has the value `c_i + c_{i + 1} x + ...
111// + c_{i + l - 1} x^{l - 1}`, and a block of `2^k` coefficients below one with value `v` merges
112// with it as `lower + x^{2^k} v`.
113//
114// This is equivalent to `fmpz_poly_evaluate_divconquer_fmpz` and
115// `_fmpz_poly_evaluate_divconquer_fmpz` from `fmpz_poly/evaluate_divconquer_fmpz.c`, FLINT 3.6.0.
116crate_test_fn! {evaluate_divide_and_conquer<T>(coefficients: &[T], x: &T) -> T
117where
118    T: Clone + Zero + for<'a> AddAssign<&'a T> + for<'a> MulAssign<&'a T>,
119    for<'a> &'a T: Mul<&'a T, Output = T> + Square<Output = T>,
120{
121    match coefficients.len() {
122        0 => return T::ZERO,
123        1 => return coefficients[0].clone(),
124        _ => {}
125    }
126    let h = usize::exact_from((coefficients.len() - 1).significant_bits());
127    // powers[k - 1] is x^{2^k}, for 1 <= k < h; x itself is borrowed rather than stored.
128    let mut powers: Vec<T> = Vec::with_capacity(h - 1);
129    for k in 1..h {
130        let square = if k == 1 {
131            x.square()
132        } else {
133            powers[k - 2].square()
134        };
135        powers.push(square);
136    }
137    let power = |k: usize| if k == 0 { x } else { &powers[k - 1] };
138    divide_and_conquer_blocks(
139        coefficients,
140        |low, high| {
141            let mut block = high * x;
142            block += low;
143            block
144        },
145        T::clone,
146        |lower, mut upper, k, _| {
147            upper *= power(k);
148            upper += lower;
149            upper
150        },
151    )
152}}
153
154// Divide and conquer only pays once its balanced multiplications are large enough for a
155// subquadratic multiplication algorithm, and when does that depends on the size of `x` as much as
156// on the number of coefficients. A one-limb `x` makes every step of Horner's rule a cheap
157// multiplication by one limb, and divide and conquer takes a long polynomial to catch up; otherwise
158// the crossover follows the product of the length and the number of limbs of `x`. The size of the
159// coefficients moves the crossover too, but both ways, and not enough to be worth a scan of them.
160//
161// Tuned on 64-bit Apple Silicon with the `evaluate` tuning level (`-g tune_evaluate`).
162pub(crate) const EVALUATE_DIVIDE_AND_CONQUER_ONE_LIMB_THRESHOLD: usize = 1024;
163pub(crate) const EVALUATE_DIVIDE_AND_CONQUER_LENGTH_TIMES_LIMBS_THRESHOLD: usize = 256;
164pub(crate) const EVALUATE_DIVIDE_AND_CONQUER_MIN_LENGTH: usize = 4;
165
166// Whether divide and conquer beats Horner's rule for a polynomial with `len` coefficients at a
167// value of `x_bits` bits.
168pub(crate) fn evaluate_use_divide_and_conquer(len: usize, x_bits: u64) -> bool {
169    let x_limbs = usize::exact_from(x_bits.div_ceil(Limb::WIDTH));
170    if x_limbs <= 1 {
171        len >= EVALUATE_DIVIDE_AND_CONQUER_ONE_LIMB_THRESHOLD
172    } else {
173        len >= EVALUATE_DIVIDE_AND_CONQUER_MIN_LENGTH
174            && len.saturating_mul(x_limbs)
175                >= EVALUATE_DIVIDE_AND_CONQUER_LENGTH_TIMES_LIMBS_THRESHOLD
176    }
177}
178
179// Evaluates a polynomial, given by its coefficients in ascending order, at `x`, choosing between
180// Horner's rule and divide and conquer by the length and the size of `x`. At `x = 1` the value is
181// the sum of the coefficients, which needs no multiplication at all.
182//
183// This is equivalent to `_fmpz_poly_evaluate_fmpz` from `fmpz_poly/evaluate_fmpz.c`, FLINT 3.6.0,
184// except for the shortcut at 1 and the choice of algorithm, where FLINT switches at 50 coefficients
185// whatever the size of `x`.
186pub(crate) fn evaluate<T>(coefficients: &[T], x: &T) -> T
187where
188    T: Clone + Zero + One + PartialEq + for<'a> AddAssign<&'a T> + for<'a> MulAssign<&'a T>,
189    T: for<'a> Sum<&'a T>,
190    for<'a> &'a T: Mul<&'a T, Output = T> + Square<Output = T> + SignificantBits,
191{
192    if *x == T::ONE {
193        coefficients.iter().sum()
194    } else if evaluate_use_divide_and_conquer(coefficients.len(), x.significant_bits()) {
195        evaluate_divide_and_conquer(coefficients, x)
196    } else {
197        evaluate_horner(coefficients, x)
198    }
199}
200
201// At `x = -1` the value is the alternating sum of the coefficients, which needs no multiplication.
202fn evaluate_integer(coefficients: &[Integer], x: &Integer) -> Integer {
203    if *x == -1i32 {
204        coefficients.iter().step_by(2).sum::<Integer>()
205            - coefficients.iter().skip(1).step_by(2).sum::<Integer>()
206    } else {
207        evaluate(coefficients, x)
208    }
209}
210
211impl Evaluate<&Integer> for &IntegerPolynomial {
212    type Output = Integer;
213
214    /// Evaluates an [`IntegerPolynomial`] at an [`Integer`], taking both by reference.
215    ///
216    /// $$
217    /// f(p, x) = \sum_{i=0}^{n-1} c_i x^i,
218    /// $$
219    ///
220    /// where $c_i$ is the coefficient of $x^i$ in $p$ and $n$ is its length. The zero polynomial
221    /// evaluates to 0 everywhere.
222    ///
223    /// Horner's rule is used unless the polynomial is long compared with the size of `x`, in which
224    /// case divide and conquer, which pairs off coefficients and merges the pairs so that each
225    /// multiplication has operands of about the same size, is faster.
226    ///
227    /// # Worst-case complexity
228    /// $T(n) = O(n \log^2 n \log\log n)$
229    ///
230    /// $M(n) = O(n \log n)$
231    ///
232    /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()` times the larger of the
233    /// greatest number of bits of any coefficient and the number of bits of `x`.
234    ///
235    /// # Examples
236    /// ```
237    /// use core::str::FromStr;
238    /// use malachite_base::num::basic::traits::{One, Two, Zero};
239    /// use malachite_base::polynomial::Evaluate;
240    /// use malachite_nz::integer::Integer;
241    /// use malachite_nz::integer_polynomial::IntegerPolynomial;
242    ///
243    /// let p = IntegerPolynomial::from_str("x^2-3*x+2").unwrap();
244    /// assert_eq!((&p).evaluate(&Integer::ZERO), 2);
245    /// assert_eq!((&p).evaluate(&Integer::ONE), 0);
246    /// assert_eq!((&p).evaluate(&Integer::from(-2)), 12);
247    /// assert_eq!((&p).evaluate(&Integer::from(10)), 72);
248    ///
249    /// let q = IntegerPolynomial::from_str("x^100-1").unwrap();
250    /// assert_eq!(
251    ///     (&q).evaluate(&Integer::TWO).to_string(),
252    ///     "1267650600228229401496703205375"
253    /// );
254    /// ```
255    ///
256    /// This is equivalent to `fmpz_poly_evaluate_fmpz` from `fmpz_poly/evaluate_fmpz.c`, FLINT
257    /// 3.6.0.
258    #[inline]
259    fn evaluate(self, x: &Integer) -> Integer {
260        evaluate_integer(&self.coefficients, x)
261    }
262}
263
264impl Evaluate<Integer> for &IntegerPolynomial {
265    type Output = Integer;
266
267    /// Evaluates an [`IntegerPolynomial`] at an [`Integer`], taking the polynomial by reference and
268    /// the value by value.
269    ///
270    /// $$
271    /// f(p, x) = \sum_{i=0}^{n-1} c_i x^i,
272    /// $$
273    ///
274    /// where $c_i$ is the coefficient of $x^i$ in $p$ and $n$ is its length. The zero polynomial
275    /// evaluates to 0 everywhere.
276    ///
277    /// Horner's rule is used unless the polynomial is long compared with the size of `x`, in which
278    /// case divide and conquer, which pairs off coefficients and merges the pairs so that each
279    /// multiplication has operands of about the same size, is faster.
280    ///
281    /// # Worst-case complexity
282    /// $T(n) = O(n \log^2 n \log\log n)$
283    ///
284    /// $M(n) = O(n \log n)$
285    ///
286    /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()` times the larger of the
287    /// greatest number of bits of any coefficient and the number of bits of `x`.
288    ///
289    /// # Examples
290    /// ```
291    /// use core::str::FromStr;
292    /// use malachite_base::num::basic::traits::{One, Two, Zero};
293    /// use malachite_base::polynomial::Evaluate;
294    /// use malachite_nz::integer::Integer;
295    /// use malachite_nz::integer_polynomial::IntegerPolynomial;
296    ///
297    /// let p = IntegerPolynomial::from_str("x^2-3*x+2").unwrap();
298    /// assert_eq!((&p).evaluate(Integer::ZERO), 2);
299    /// assert_eq!((&p).evaluate(Integer::ONE), 0);
300    /// assert_eq!((&p).evaluate(Integer::from(-2)), 12);
301    /// assert_eq!((&p).evaluate(Integer::from(10)), 72);
302    ///
303    /// let q = IntegerPolynomial::from_str("x^100-1").unwrap();
304    /// assert_eq!(
305    ///     (&q).evaluate(Integer::TWO).to_string(),
306    ///     "1267650600228229401496703205375"
307    /// );
308    /// ```
309    ///
310    /// This is equivalent to `fmpz_poly_evaluate_fmpz` from `fmpz_poly/evaluate_fmpz.c`, FLINT
311    /// 3.6.0.
312    #[inline]
313    fn evaluate(self, x: Integer) -> Integer {
314        evaluate_integer(&self.coefficients, &x)
315    }
316}
317
318// Returns `c` modulo `m`, in $[0, m)$, where `m_natural` is `m` as a `Natural`.
319fn integer_mod_u64(c: &Integer, m: u64, m_natural: &Natural) -> u64 {
320    let r = u64::exact_from(&(&c.abs % m_natural));
321    if c.sign || r == 0 { r } else { m - r }
322}
323
324impl ModEvaluate<u64> for &IntegerPolynomial {
325    type Output = u64;
326
327    /// Evaluates an [`IntegerPolynomial`] at a [`u64`], modulo a [`u64`]. The coefficients may be
328    /// any [`Integer`]s, and are reduced as the evaluation goes; `x` must already be reduced modulo
329    /// `m`.
330    ///
331    /// $$
332    /// f(p, x, m) = \left ( \sum_{i=0}^{n-1} c_i x^i \right ) \bmod m,
333    /// $$
334    ///
335    /// where $c_i$ is the coefficient of $x^i$ in $p$, $n$ is its length, and the result is in $[0,
336    /// m)$. The zero polynomial evaluates to 0 everywhere.
337    ///
338    /// Each coefficient is reduced to a word, and the words are then evaluated as by
339    /// [`UnsignedPolynomial::mod_evaluate`](malachite_base::polynomial::ModEvaluate::mod_evaluate),
340    /// with Horner's rule and, for longer polynomials, Shoup's method.
341    ///
342    /// # Worst-case complexity
343    /// $T(n, m) = O(n + m)$
344    ///
345    /// $M(m) = O(m)$
346    ///
347    /// where $T$ is time, $M$ is additional memory, $n$ is the total number of bits of the
348    /// coefficients, and $m$ is `self.len()`.
349    ///
350    /// # Panics
351    /// Panics if `m` is 0, or if `x` is greater than or equal to `m`.
352    ///
353    /// # Examples
354    /// ```
355    /// use core::str::FromStr;
356    /// use malachite_base::polynomial::ModEvaluate;
357    /// use malachite_nz::integer_polynomial::IntegerPolynomial;
358    ///
359    /// let p = IntegerPolynomial::from_str("-5*x^2+3*x-7").unwrap();
360    /// // -5 * 36 + 3 * 6 - 7 = -169, which is 7 mod 11.
361    /// assert_eq!((&p).mod_evaluate(6, 11), 7);
362    /// // The coefficients need not be reduced.
363    /// let p = IntegerPolynomial::from_str("100*x+1").unwrap();
364    /// assert_eq!((&p).mod_evaluate(3, 10), 1);
365    /// ```
366    ///
367    /// This is equivalent to `fmpz_poly_evaluate_mod` from `fmpz_poly/mod_evaluate.c`, FLINT 3.6.0,
368    /// except that `x` must be reduced.
369    fn mod_evaluate(self, x: u64, m: u64) -> u64 {
370        assert_ne!(m, 0, "m cannot be 0");
371        assert!(x < m, "x must be reduced mod m, but {x} >= {m}");
372        let m_natural = Natural::from(m);
373        match self.coefficients.as_slice() {
374            [] => 0,
375            [c, ..] if x == 0 => integer_mod_u64(c, m, &m_natural),
376            coefficients => {
377                let reduced: Vec<u64> = coefficients
378                    .iter()
379                    .map(|c| integer_mod_u64(c, m, &m_natural))
380                    .collect();
381                mod_evaluate_slice(&reduced, x, m)
382            }
383        }
384    }
385}
386
387impl EvaluateMany<Integer> for &IntegerPolynomial {
388    type Output = Integer;
389
390    /// Evaluates an [`IntegerPolynomial`] at each of several [`Integer`]s.
391    ///
392    /// $$
393    /// f(p, (x_j)_{j=0}^{k-1}) = \left ( \sum_{i=0}^{n-1} c_i x_j^i \right )_{j=0}^{k-1},
394    /// $$
395    ///
396    /// where $c_i$ is the coefficient of $x^i$ in $p$ and $n$ is its length.
397    ///
398    /// Each value is found as by [`evaluate`](malachite_base::polynomial::Evaluate::evaluate),
399    /// which chooses between Horner's rule and divide and conquer by the length of the polynomial
400    /// and the size of the value.
401    ///
402    /// # Worst-case complexity
403    /// $T(n, k) = O(kn \log^2 n \log\log n)$
404    ///
405    /// $M(n, k) = O(kn \log n)$
406    ///
407    /// where $T$ is time, $M$ is additional memory, $k$ is `xs.len()`, and $n$ is `self.len()`
408    /// times the larger of the greatest number of bits of any coefficient and the greatest number
409    /// of bits of any value in `xs`.
410    ///
411    /// # Examples
412    /// ```
413    /// use core::str::FromStr;
414    /// use malachite_base::polynomial::EvaluateMany;
415    /// use malachite_nz::integer::Integer;
416    /// use malachite_nz::integer_polynomial::IntegerPolynomial;
417    ///
418    /// let p = IntegerPolynomial::from_str("x^2-3*x+2").unwrap();
419    /// let xs = [-1i32, 0, 1, 2, 3].map(Integer::from);
420    /// assert_eq!(
421    ///     (&p).evaluate_many(&xs),
422    ///     [6i32, 2, 0, 0, 2].map(Integer::from)
423    /// );
424    /// ```
425    ///
426    /// This is equivalent to `fmpz_poly_evaluate_fmpz_vec` from `fmpz_poly/evaluate_fmpz_vec.c`,
427    /// FLINT 3.6.0.
428    #[inline]
429    fn evaluate_many(self, xs: &[Integer]) -> Vec<Integer> {
430        xs.iter().map(|x| self.evaluate(x)).collect()
431    }
432}