Skip to main content

malachite_nz/gaussian_integer/arithmetic/
square.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// Uses code adopted from the FLINT Library.
4//
5//      Copyright © 2022 Fredrik Johansson
6//
7// This file is part of Malachite.
8//
9// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
10// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
11// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
12
13use crate::gaussian_integer::GaussianInteger;
14use crate::integer::Integer;
15use core::mem::take;
16use malachite_base::num::arithmetic::traits::{Square, SquareAssign};
17use malachite_base::num::basic::traits::Zero;
18use malachite_base::num::logic::traits::SignificantBits;
19
20use crate::gaussian_integer::arithmetic::SIZE_BALANCE_BITS;
21
22// This threshold is from `fmpzi_sqr` in FLINT 3.6.0, where it is a limb count (16 limbs, with
23// 64-bit limbs); it is expressed here in bits so that it does not shift when Malachite is built
24// with 32-bit limbs.
25const THREE_SQUARES_THRESHOLD_BITS: u64 = 16 * 64;
26
27enum SquareAlgorithm {
28    DoubleWord(i64, i64),
29    PurelyReal,
30    PurelyImaginary,
31    ThreeSquares,
32    General,
33}
34
35// The algorithm selection of fmpzi_sqr from fmpzi/sqr.c, FLINT 3.6.0. Every path uses three
36// multiplications, but arranges for as many of them as possible to be squarings, which are cheaper
37// than general multiplications.
38fn choose_algorithm(x: &GaussianInteger) -> SquareAlgorithm {
39    // If both parts fit in a signed word, three double-word products suffice.
40    if let (Ok(a), Ok(b)) = (i64::try_from(&x.real), i64::try_from(&x.imaginary)) {
41        return SquareAlgorithm::DoubleWord(a, b);
42    }
43    if x.imaginary == 0u32 {
44        return SquareAlgorithm::PurelyReal;
45    }
46    if x.real == 0u32 {
47        return SquareAlgorithm::PurelyImaginary;
48    }
49    // For large, balanced operands, three squarings: with $t = a^2$ and $v = b^2$, the real part is
50    // $t - v$ and the imaginary part is $(a + b)^2 - t - v$.
51    let a_bits = x.real.significant_bits();
52    if a_bits >= THREE_SQUARES_THRESHOLD_BITS {
53        let b_bits = x.imaginary.significant_bits();
54        if a_bits.abs_diff(b_bits) <= SIZE_BALANCE_BITS {
55            return SquareAlgorithm::ThreeSquares;
56        }
57    }
58    // Otherwise, two squarings and one general multiplication: $a^2 - b^2$ and $2ab$.
59    SquareAlgorithm::General
60}
61
62// The squares of two `i64`s and their sums and differences cannot overflow an `i128`.
63fn square_double_word(a: i64, b: i64) -> GaussianInteger {
64    let (a, b) = (i128::from(a), i128::from(b));
65    GaussianInteger {
66        real: Integer::from(a * a - b * b),
67        imaginary: Integer::from((a * b) << 1u64),
68    }
69}
70
71// Each part appears in exactly two products, so an owned part is borrowed by its first use and
72// consumed by its last, letting the products reuse the operand's storage.
73fn square_val(x: GaussianInteger) -> GaussianInteger {
74    match choose_algorithm(&x) {
75        SquareAlgorithm::DoubleWord(a, b) => square_double_word(a, b),
76        SquareAlgorithm::PurelyReal => GaussianInteger {
77            real: x.real.square(),
78            imaginary: Integer::ZERO,
79        },
80        SquareAlgorithm::PurelyImaginary => GaussianInteger {
81            real: -x.imaginary.square(),
82            imaginary: Integer::ZERO,
83        },
84        SquareAlgorithm::ThreeSquares => {
85            let mut u = (&x.real + &x.imaginary).square();
86            let t = x.real.square();
87            let v = x.imaginary.square();
88            u -= &t;
89            u -= &v;
90            GaussianInteger {
91                real: t - v,
92                imaginary: u,
93            }
94        }
95        SquareAlgorithm::General => {
96            let real = (&x.real).square() - (&x.imaginary).square();
97            GaussianInteger {
98                real,
99                imaginary: (x.real * x.imaginary) << 1u32,
100            }
101        }
102    }
103}
104
105fn square_ref(x: &GaussianInteger) -> GaussianInteger {
106    match choose_algorithm(x) {
107        SquareAlgorithm::DoubleWord(a, b) => square_double_word(a, b),
108        SquareAlgorithm::PurelyReal => GaussianInteger {
109            real: (&x.real).square(),
110            imaginary: Integer::ZERO,
111        },
112        SquareAlgorithm::PurelyImaginary => GaussianInteger {
113            real: -(&x.imaginary).square(),
114            imaginary: Integer::ZERO,
115        },
116        SquareAlgorithm::ThreeSquares => {
117            let mut u = (&x.real + &x.imaginary).square();
118            let t = (&x.real).square();
119            let v = (&x.imaginary).square();
120            u -= &t;
121            u -= &v;
122            GaussianInteger {
123                real: t - v,
124                imaginary: u,
125            }
126        }
127        SquareAlgorithm::General => GaussianInteger {
128            real: (&x.real).square() - (&x.imaginary).square(),
129            imaginary: (&x.real * &x.imaginary) << 1u32,
130        },
131    }
132}
133
134impl Square for GaussianInteger {
135    type Output = Self;
136
137    /// Squares a [`GaussianInteger`], taking it by value.
138    ///
139    /// $$
140    /// f(x) = x^2.
141    /// $$
142    ///
143    /// # Worst-case complexity
144    /// $T(n) = O(n \log n \log\log n)$
145    ///
146    /// $M(n) = O(n \log n)$
147    ///
148    /// where $T$ is time, $M$ is additional memory, and $n$ is the maximum number of significant
149    /// bits of the real and imaginary parts.
150    ///
151    /// # Examples
152    /// ```
153    /// use malachite_base::num::arithmetic::traits::Square;
154    /// use malachite_base::num::basic::traits::I;
155    /// use malachite_nz::gaussian_integer::GaussianInteger;
156    /// use std::str::FromStr;
157    ///
158    /// assert_eq!(GaussianInteger::I.square().to_string(), "-1");
159    /// let x = GaussianInteger::from_str("2-3i").unwrap();
160    /// assert_eq!(x.square().to_string(), "-5-12i");
161    /// ```
162    #[inline]
163    fn square(self) -> Self {
164        square_val(self)
165    }
166}
167
168impl Square for &GaussianInteger {
169    type Output = GaussianInteger;
170
171    /// Squares a [`GaussianInteger`], taking it by reference.
172    ///
173    /// $$
174    /// f(x) = x^2.
175    /// $$
176    ///
177    /// # Worst-case complexity
178    /// $T(n) = O(n \log n \log\log n)$
179    ///
180    /// $M(n) = O(n \log n)$
181    ///
182    /// where $T$ is time, $M$ is additional memory, and $n$ is the maximum number of significant
183    /// bits of the real and imaginary parts.
184    ///
185    /// # Examples
186    /// ```
187    /// use malachite_base::num::arithmetic::traits::Square;
188    /// use malachite_nz::gaussian_integer::GaussianInteger;
189    /// use std::str::FromStr;
190    ///
191    /// let x = GaussianInteger::from_str("2-3i").unwrap();
192    /// assert_eq!((&x).square().to_string(), "-5-12i");
193    /// ```
194    #[inline]
195    fn square(self) -> GaussianInteger {
196        square_ref(self)
197    }
198}
199
200impl SquareAssign for GaussianInteger {
201    /// Squares a [`GaussianInteger`] in place.
202    ///
203    /// $$
204    /// x \gets x^2.
205    /// $$
206    ///
207    /// # Worst-case complexity
208    /// $T(n) = O(n \log n \log\log n)$
209    ///
210    /// $M(n) = O(n \log n)$
211    ///
212    /// where $T$ is time, $M$ is additional memory, and $n$ is the maximum number of significant
213    /// bits of the real and imaginary parts.
214    ///
215    /// # Examples
216    /// ```
217    /// use malachite_base::num::arithmetic::traits::SquareAssign;
218    /// use malachite_nz::gaussian_integer::GaussianInteger;
219    /// use std::str::FromStr;
220    ///
221    /// let mut x = GaussianInteger::from_str("2-3i").unwrap();
222    /// x.square_assign();
223    /// assert_eq!(x.to_string(), "-5-12i");
224    /// ```
225    #[inline]
226    fn square_assign(&mut self) {
227        *self = square_val(take(self));
228    }
229}