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}