Skip to main content

malachite_q/rational_vector/arithmetic/
dot_sign.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::Rational;
10use crate::rational_vector::RationalVector;
11use core::cmp::Ordering::{self, *};
12use malachite_base::num::arithmetic::traits::DotSign;
13use malachite_base::num::comparison::traits::OrdProductsNeg;
14use malachite_base::num::conversion::traits::ExactFrom;
15use malachite_base::num::logic::traits::SignificantBits;
16
17// What one pass over the terms finds out, for one sign: how many nonzero terms have it, and the
18// index and size exponent of the largest of them. A term `(n_x n_y) / (d_x d_y)` with exponent `e =
19// bits(n_x) + bits(n_y) - bits(d_x) - bits(d_y)` lies in `[2^(e - 2), 2^(e + 2))`.
20#[derive(Clone, Copy, Default)]
21struct Side {
22    count: u64,
23    max_exponent: i64,
24    max_index: usize,
25}
26
27impl Side {
28    const fn add(&mut self, index: usize, exponent: i64) {
29        if self.count == 0 || exponent > self.max_exponent {
30            self.max_exponent = exponent;
31            self.max_index = index;
32        }
33        self.count += 1;
34    }
35
36    // An upper bound on the size exponent of the side's sum: each of the `count` terms is below
37    // `2^(max_exponent + 2)`.
38    fn sum_exponent_upper_bound(self) -> i64 {
39        self.max_exponent + 2 + i64::exact_from(self.count.significant_bits())
40    }
41}
42
43fn exponent(x: &Rational) -> i64 {
44    i64::exact_from(x.numerator.significant_bits())
45        - i64::exact_from(x.denominator.significant_bits())
46}
47
48// The sign of the dot product of `xs` and `ys`, which have the same length, if it can be found
49// without writing the vectors over common denominators: two-dimensional vectors, and otherwise an
50// empty side, one term on each side, or sizes far enough apart.
51fn dot_sign_screens(xs: &[Rational], ys: &[Rational]) -> Option<Ordering> {
52    assert_eq!(
53        xs.len(),
54        ys.len(),
55        "the vectors must have the same dimension"
56    );
57    // In two dimensions the sign of `x_0 y_0 + x_1 y_1` is the comparison of `x_0 y_0` with `-x_1
58    // y_1`, which `cmp_products_neg` makes with all of its screens.
59    if let ([x_0, x_1], [y_0, y_1]) = (xs, ys) {
60        return Some(Rational::cmp_products_neg(x_0, y_0, x_1, y_1));
61    }
62    let mut positive = Side::default();
63    let mut negative = Side::default();
64    for (i, (x, y)) in xs.iter().zip(ys).enumerate() {
65        if x.numerator == 0u32 || y.numerator == 0u32 {
66            continue;
67        }
68        let e = exponent(x) + exponent(y);
69        if x.sign == y.sign {
70            positive.add(i, e);
71        } else {
72            negative.add(i, e);
73        }
74    }
75    match (positive.count, negative.count) {
76        (0, 0) => return Some(Equal),
77        (_, 0) => return Some(Greater),
78        (0, _) => return Some(Less),
79        (1, 1) => {
80            let (p, n) = (positive.max_index, negative.max_index);
81            return Some(Rational::cmp_products_neg(&xs[p], &ys[p], &xs[n], &ys[n]));
82        }
83        _ => {}
84    }
85    // A side's largest term is at least `2^(max_exponent - 2)`, so it alone outweighs a whole other
86    // side whose sum is below that.
87    if positive.sum_exponent_upper_bound() + 2 <= negative.max_exponent {
88        Some(Less)
89    } else if negative.sum_exponent_upper_bound() + 2 <= positive.max_exponent {
90        Some(Greater)
91    } else {
92        None
93    }
94}
95
96impl DotSign<Self> for RationalVector {
97    /// Determines the sign of the dot product of two [`RationalVector`]s, taking the first by value
98    /// and the second by value.
99    ///
100    /// The result is the ordering of the dot product against 0, found with as little arithmetic as
101    /// possible. For two-dimensional vectors it is
102    /// [`Rational::cmp_products_neg`](OrdProductsNeg::cmp_products_neg) on the two pairs of
103    /// elements. Otherwise the terms are split by sign, and when one side is empty, when there is
104    /// one term on each side (also compared through `cmp_products_neg`), or when the sizes of the
105    /// largest terms on the two sides are far enough apart, no product is formed. Only then are the
106    /// vectors written as integer numerators over common denominators, whose dot product has the
107    /// same sign, as in [`Dot`](malachite_base::num::arithmetic::traits::Dot); its sign is found by
108    /// [`IntegerVector::dot_sign`](DotSign::dot_sign).
109    ///
110    /// $$
111    /// f(v, w) = \operatorname{sgn}\left ( \sum_{i=0}^{n-1} v_iw_i \right ),
112    /// $$
113    ///
114    /// where $n$ is the dimension of $v$ and $w$.
115    ///
116    /// # Worst-case complexity
117    /// $T(n, m) = O(nm \log m \log\log m)$
118    ///
119    /// $M(n, m) = O(nm)$
120    ///
121    /// where $T$ is time, $M$ is additional memory, $n$ is the dimension of the vectors, and $m$ is
122    /// the largest number of significant bits of the numerators the elements take over each
123    /// vector's common denominator.
124    ///
125    /// # Panics
126    /// Panics if the vectors have different dimensions.
127    ///
128    /// # Examples
129    /// ```
130    /// use core::cmp::Ordering::*;
131    /// use core::str::FromStr;
132    /// use malachite_base::num::arithmetic::traits::DotSign;
133    /// use malachite_q::rational_vector::RationalVector;
134    ///
135    /// let v = RationalVector::from_str("(1/2, 1/3, 1/6)").unwrap();
136    /// let w = RationalVector::from_str("(1, 1, -4)").unwrap();
137    /// assert_eq!(v.clone().dot_sign(w.clone()), Greater);
138    /// let w = RationalVector::from_str("(1, 1, -5)").unwrap();
139    /// assert_eq!(v.clone().dot_sign(w.clone()), Equal);
140    /// ```
141    #[inline]
142    fn dot_sign(self, other: Self) -> Ordering {
143        dot_sign_screens(&self.elements, &other.elements).unwrap_or_else(|| {
144            // The common denominators are positive, so the dot product of the numerators has the
145            // sign of the dot product.
146            self.into_numerators_and_denominator()
147                .0
148                .dot_sign(other.into_numerators_and_denominator().0)
149        })
150    }
151}
152
153impl DotSign<&Self> for RationalVector {
154    /// Determines the sign of the dot product of two [`RationalVector`]s, taking the first by value
155    /// and the second by reference.
156    ///
157    /// The result is the ordering of the dot product against 0, found with as little arithmetic as
158    /// possible. For two-dimensional vectors it is
159    /// [`Rational::cmp_products_neg`](OrdProductsNeg::cmp_products_neg) on the two pairs of
160    /// elements. Otherwise the terms are split by sign, and when one side is empty, when there is
161    /// one term on each side (also compared through `cmp_products_neg`), or when the sizes of the
162    /// largest terms on the two sides are far enough apart, no product is formed. Only then are the
163    /// vectors written as integer numerators over common denominators, whose dot product has the
164    /// same sign, as in [`Dot`](malachite_base::num::arithmetic::traits::Dot); its sign is found by
165    /// [`IntegerVector::dot_sign`](DotSign::dot_sign).
166    ///
167    /// $$
168    /// f(v, w) = \operatorname{sgn}\left ( \sum_{i=0}^{n-1} v_iw_i \right ),
169    /// $$
170    ///
171    /// where $n$ is the dimension of $v$ and $w$.
172    ///
173    /// # Worst-case complexity
174    /// $T(n, m) = O(nm \log m \log\log m)$
175    ///
176    /// $M(n, m) = O(nm)$
177    ///
178    /// where $T$ is time, $M$ is additional memory, $n$ is the dimension of the vectors, and $m$ is
179    /// the largest number of significant bits of the numerators the elements take over each
180    /// vector's common denominator.
181    ///
182    /// # Panics
183    /// Panics if the vectors have different dimensions.
184    ///
185    /// # Examples
186    /// ```
187    /// use core::cmp::Ordering::*;
188    /// use core::str::FromStr;
189    /// use malachite_base::num::arithmetic::traits::DotSign;
190    /// use malachite_q::rational_vector::RationalVector;
191    ///
192    /// let v = RationalVector::from_str("(1/2, 1/3, 1/6)").unwrap();
193    /// let w = RationalVector::from_str("(1, 1, -4)").unwrap();
194    /// assert_eq!(v.clone().dot_sign(&w), Greater);
195    /// let w = RationalVector::from_str("(1, 1, -5)").unwrap();
196    /// assert_eq!(v.clone().dot_sign(&w), Equal);
197    /// ```
198    #[inline]
199    fn dot_sign(self, other: &Self) -> Ordering {
200        dot_sign_screens(&self.elements, &other.elements).unwrap_or_else(|| {
201            // The common denominators are positive, so the dot product of the numerators has the
202            // sign of the dot product.
203            self.into_numerators_and_denominator()
204                .0
205                .dot_sign(other.to_numerators_and_denominator().0)
206        })
207    }
208}
209
210impl DotSign<RationalVector> for &RationalVector {
211    /// Determines the sign of the dot product of two [`RationalVector`]s, taking the first by
212    /// reference and the second by value.
213    ///
214    /// The result is the ordering of the dot product against 0, found with as little arithmetic as
215    /// possible. For two-dimensional vectors it is
216    /// [`Rational::cmp_products_neg`](OrdProductsNeg::cmp_products_neg) on the two pairs of
217    /// elements. Otherwise the terms are split by sign, and when one side is empty, when there is
218    /// one term on each side (also compared through `cmp_products_neg`), or when the sizes of the
219    /// largest terms on the two sides are far enough apart, no product is formed. Only then are the
220    /// vectors written as integer numerators over common denominators, whose dot product has the
221    /// same sign, as in [`Dot`](malachite_base::num::arithmetic::traits::Dot); its sign is found by
222    /// [`IntegerVector::dot_sign`](DotSign::dot_sign).
223    ///
224    /// $$
225    /// f(v, w) = \operatorname{sgn}\left ( \sum_{i=0}^{n-1} v_iw_i \right ),
226    /// $$
227    ///
228    /// where $n$ is the dimension of $v$ and $w$.
229    ///
230    /// # Worst-case complexity
231    /// $T(n, m) = O(nm \log m \log\log m)$
232    ///
233    /// $M(n, m) = O(nm)$
234    ///
235    /// where $T$ is time, $M$ is additional memory, $n$ is the dimension of the vectors, and $m$ is
236    /// the largest number of significant bits of the numerators the elements take over each
237    /// vector's common denominator.
238    ///
239    /// # Panics
240    /// Panics if the vectors have different dimensions.
241    ///
242    /// # Examples
243    /// ```
244    /// use core::cmp::Ordering::*;
245    /// use core::str::FromStr;
246    /// use malachite_base::num::arithmetic::traits::DotSign;
247    /// use malachite_q::rational_vector::RationalVector;
248    ///
249    /// let v = RationalVector::from_str("(1/2, 1/3, 1/6)").unwrap();
250    /// let w = RationalVector::from_str("(1, 1, -4)").unwrap();
251    /// assert_eq!((&v).dot_sign(w.clone()), Greater);
252    /// let w = RationalVector::from_str("(1, 1, -5)").unwrap();
253    /// assert_eq!((&v).dot_sign(w.clone()), Equal);
254    /// ```
255    #[inline]
256    fn dot_sign(self, other: RationalVector) -> Ordering {
257        dot_sign_screens(&self.elements, &other.elements).unwrap_or_else(|| {
258            // The common denominators are positive, so the dot product of the numerators has the
259            // sign of the dot product.
260            self.to_numerators_and_denominator()
261                .0
262                .dot_sign(other.into_numerators_and_denominator().0)
263        })
264    }
265}
266
267impl DotSign<&RationalVector> for &RationalVector {
268    /// Determines the sign of the dot product of two [`RationalVector`]s, taking the first by
269    /// reference and the second by reference.
270    ///
271    /// The result is the ordering of the dot product against 0, found with as little arithmetic as
272    /// possible. For two-dimensional vectors it is
273    /// [`Rational::cmp_products_neg`](OrdProductsNeg::cmp_products_neg) on the two pairs of
274    /// elements. Otherwise the terms are split by sign, and when one side is empty, when there is
275    /// one term on each side (also compared through `cmp_products_neg`), or when the sizes of the
276    /// largest terms on the two sides are far enough apart, no product is formed. Only then are the
277    /// vectors written as integer numerators over common denominators, whose dot product has the
278    /// same sign, as in [`Dot`](malachite_base::num::arithmetic::traits::Dot); its sign is found by
279    /// [`IntegerVector::dot_sign`](DotSign::dot_sign).
280    ///
281    /// $$
282    /// f(v, w) = \operatorname{sgn}\left ( \sum_{i=0}^{n-1} v_iw_i \right ),
283    /// $$
284    ///
285    /// where $n$ is the dimension of $v$ and $w$.
286    ///
287    /// # Worst-case complexity
288    /// $T(n, m) = O(nm \log m \log\log m)$
289    ///
290    /// $M(n, m) = O(nm)$
291    ///
292    /// where $T$ is time, $M$ is additional memory, $n$ is the dimension of the vectors, and $m$ is
293    /// the largest number of significant bits of the numerators the elements take over each
294    /// vector's common denominator.
295    ///
296    /// # Panics
297    /// Panics if the vectors have different dimensions.
298    ///
299    /// # Examples
300    /// ```
301    /// use core::cmp::Ordering::*;
302    /// use core::str::FromStr;
303    /// use malachite_base::num::arithmetic::traits::DotSign;
304    /// use malachite_q::rational_vector::RationalVector;
305    ///
306    /// let v = RationalVector::from_str("(1/2, 1/3, 1/6)").unwrap();
307    /// let w = RationalVector::from_str("(1, 1, -4)").unwrap();
308    /// assert_eq!((&v).dot_sign(&w), Greater);
309    /// let w = RationalVector::from_str("(1, 1, -5)").unwrap();
310    /// assert_eq!((&v).dot_sign(&w), Equal);
311    /// ```
312    #[inline]
313    fn dot_sign(self, other: &RationalVector) -> Ordering {
314        dot_sign_screens(&self.elements, &other.elements).unwrap_or_else(|| {
315            // The common denominators are positive, so the dot product of the numerators has the
316            // sign of the dot product.
317            self.to_numerators_and_denominator()
318                .0
319                .dot_sign(other.to_numerators_and_denominator().0)
320        })
321    }
322}