Skip to main content

malachite_nz/gaussian_integer/arithmetic/
sqrt.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::gaussian_integer::{ComparableGaussianIntegerRef, GaussianInteger};
10use crate::integer::Integer;
11use crate::natural::Natural;
12use alloc::vec::Vec;
13use malachite_base::num::arithmetic::traits::{CheckedSqrt, Parity, Square, UnsignedAbs};
14use malachite_base::num::basic::traits::Zero;
15
16// If a + bi = (x + yi)^2 then, with N = sqrt(a^2 + b^2), x^2 = (N + a) / 2 and y^2 = (N - a) / 2,
17// and 2xy = b fixes the sign of y once x is taken positive. A nonzero square root is normalized to
18// the principal one, with positive real part or, failing that, non-negative imaginary part.
19fn checked_sqrt_helper(z: &GaussianInteger) -> Option<GaussianInteger> {
20    let a = &z.real;
21    let b = &z.imaginary;
22    if *b == 0u32 {
23        let root = Integer::from(a.unsigned_abs_ref().checked_sqrt()?);
24        return Some(if *a >= 0u32 {
25            GaussianInteger::from(root)
26        } else {
27            // sqrt(-n) = sqrt(n) i
28            GaussianInteger {
29                real: Integer::ZERO,
30                imaginary: root,
31            }
32        });
33    } else if *a == 0u32 {
34        // (x + xi)^2 = 2x^2 i and (x - xi)^2 = -2x^2 i
35        if b.odd() {
36            return None;
37        }
38        let root = Integer::from((b.unsigned_abs_ref() >> 1u32).checked_sqrt()?);
39        return Some(GaussianInteger {
40            imaginary: if *b > 0u32 { root.clone() } else { -&root },
41            real: root,
42        });
43    }
44    let norm: Natural = a.unsigned_abs_ref().square() + b.unsigned_abs_ref().square();
45    let n = Integer::from(norm.checked_sqrt()?);
46    let x_squared = &n + a;
47    if x_squared.odd() {
48        return None;
49    }
50    let x = (x_squared >> 1u32).unsigned_abs().checked_sqrt()?;
51    let y = ((n - a) >> 1u32).unsigned_abs().checked_sqrt()?;
52    Some(GaussianInteger {
53        real: Integer::from(x),
54        imaginary: Integer::from_sign_and_abs(*b > 0u32, y),
55    })
56}
57
58impl CheckedSqrt for GaussianInteger {
59    type Output = Self;
60
61    /// Returns the principal square root of a [`GaussianInteger`], or `None` if it is not a perfect
62    /// square. The [`GaussianInteger`] is taken by value.
63    ///
64    /// A nonzero Gaussian integer that is a perfect square has two square roots, each the negative
65    /// of the other; the one returned is the principal root, whose real part is positive or, if it
66    /// is zero, whose imaginary part is non-negative. That is the root whose argument lies in
67    /// $(-\pi/2, \pi/2]$.
68    ///
69    /// The root is found through the norm: if $a + bi = (x + yi)^2$ then $N = \sqrt{a^2 + b^2}$ is
70    /// an integer, $x^2 = (N + a) / 2$, $y^2 = (N - a) / 2$, and $2xy = b$ fixes the sign of $y$
71    /// relative to that of $x$.
72    ///
73    /// $$
74    /// f(z) = \begin{cases}
75    ///     \operatorname{Some}(\sqrt{z}) & \text{if} \quad \sqrt{z} \in \Z\[i\], \\\\
76    ///     \operatorname{None} & \textrm{otherwise}.
77    /// \end{cases}
78    /// $$
79    ///
80    /// # Worst-case complexity
81    /// $T(n) = O(n \log n \log\log n)$
82    ///
83    /// $M(n) = O(n \log n)$
84    ///
85    /// where $T$ is time, $M$ is additional memory, and $n$ is the maximum number of significant
86    /// bits of the real and imaginary parts of `self`.
87    ///
88    /// # Examples
89    /// ```
90    /// use malachite_base::num::arithmetic::traits::CheckedSqrt;
91    /// use malachite_nz::gaussian_integer::GaussianInteger;
92    /// use std::str::FromStr;
93    ///
94    /// // (2+i)^2 = 3+4i
95    /// assert_eq!(
96    ///     GaussianInteger::from_str("3+4i")
97    ///         .unwrap()
98    ///         .checked_sqrt()
99    ///         .unwrap()
100    ///         .to_string(),
101    ///     "2+i"
102    /// );
103    /// // (1-i)^2 = -2i
104    /// assert_eq!(
105    ///     GaussianInteger::from_str("-2i")
106    ///         .unwrap()
107    ///         .checked_sqrt()
108    ///         .unwrap()
109    ///         .to_string(),
110    ///     "1-i"
111    /// );
112    /// // -4 = (2i)^2, and 2i is the principal root
113    /// assert_eq!(
114    ///     GaussianInteger::from(-4)
115    ///         .checked_sqrt()
116    ///         .unwrap()
117    ///         .to_string(),
118    ///     "2i"
119    /// );
120    /// assert!(
121    ///     GaussianInteger::from_str("2+i")
122    ///         .unwrap()
123    ///         .checked_sqrt()
124    ///         .is_none()
125    /// );
126    /// ```
127    #[inline]
128    fn checked_sqrt(self) -> Option<Self> {
129        checked_sqrt_helper(&self)
130    }
131}
132
133impl CheckedSqrt for &GaussianInteger {
134    type Output = GaussianInteger;
135
136    /// Returns the principal square root of a [`GaussianInteger`], or `None` if it is not a perfect
137    /// square. The [`GaussianInteger`] is taken by reference.
138    ///
139    /// A nonzero Gaussian integer that is a perfect square has two square roots, each the negative
140    /// of the other; the one returned is the principal root, whose real part is positive or, if it
141    /// is zero, whose imaginary part is non-negative. That is the root whose argument lies in
142    /// $(-\pi/2, \pi/2]$.
143    ///
144    /// The root is found through the norm: if $a + bi = (x + yi)^2$ then $N = \sqrt{a^2 + b^2}$ is
145    /// an integer, $x^2 = (N + a) / 2$, $y^2 = (N - a) / 2$, and $2xy = b$ fixes the sign of $y$
146    /// relative to that of $x$.
147    ///
148    /// $$
149    /// f(z) = \begin{cases}
150    ///     \operatorname{Some}(\sqrt{z}) & \text{if} \quad \sqrt{z} \in \Z\[i\], \\\\
151    ///     \operatorname{None} & \textrm{otherwise}.
152    /// \end{cases}
153    /// $$
154    ///
155    /// # Worst-case complexity
156    /// $T(n) = O(n \log n \log\log n)$
157    ///
158    /// $M(n) = O(n \log n)$
159    ///
160    /// where $T$ is time, $M$ is additional memory, and $n$ is the maximum number of significant
161    /// bits of the real and imaginary parts of `self`.
162    ///
163    /// # Examples
164    /// ```
165    /// use malachite_base::num::arithmetic::traits::CheckedSqrt;
166    /// use malachite_nz::gaussian_integer::GaussianInteger;
167    /// use std::str::FromStr;
168    ///
169    /// // (2+i)^2 = 3+4i
170    /// assert_eq!(
171    ///     (&GaussianInteger::from_str("3+4i").unwrap())
172    ///         .checked_sqrt()
173    ///         .unwrap()
174    ///         .to_string(),
175    ///     "2+i"
176    /// );
177    /// // (1-i)^2 = -2i
178    /// assert_eq!(
179    ///     (&GaussianInteger::from_str("-2i").unwrap())
180    ///         .checked_sqrt()
181    ///         .unwrap()
182    ///         .to_string(),
183    ///     "1-i"
184    /// );
185    /// // -4 = (2i)^2, and 2i is the principal root
186    /// assert_eq!(
187    ///     (&GaussianInteger::from(-4))
188    ///         .checked_sqrt()
189    ///         .unwrap()
190    ///         .to_string(),
191    ///     "2i"
192    /// );
193    /// assert!(
194    ///     (&GaussianInteger::from_str("2+i").unwrap())
195    ///         .checked_sqrt()
196    ///         .is_none()
197    /// );
198    /// ```
199    #[inline]
200    fn checked_sqrt(self) -> Option<GaussianInteger> {
201        checked_sqrt_helper(self)
202    }
203}
204
205impl GaussianInteger {
206    /// Returns all the square roots of a [`GaussianInteger`]: none if it is not a perfect square,
207    /// one if it is zero, and otherwise the principal root and its negative, in the canonical order
208    /// of [`ComparableGaussianInteger`](crate::gaussian_integer::ComparableGaussianInteger),
209    /// lexicographic by real part and then imaginary part.
210    ///
211    /// The principal root is the one with positive real part or, if that is zero, with non-negative
212    /// imaginary part; see [`CheckedSqrt`].
213    ///
214    /// $$
215    /// f(z) = \\{ w \in \Z\[i\] : w^2 = z \\}.
216    /// $$
217    ///
218    /// # Worst-case complexity
219    /// $T(n) = O(n \log n \log\log n)$
220    ///
221    /// $M(n) = O(n \log n)$
222    ///
223    /// where $T$ is time, $M$ is additional memory, and $n$ is the maximum number of significant
224    /// bits of the real and imaginary parts of `self`.
225    ///
226    /// # Examples
227    /// ```
228    /// use malachite_base::num::basic::traits::Zero;
229    /// use malachite_nz::gaussian_integer::GaussianInteger;
230    /// use std::str::FromStr;
231    ///
232    /// let roots = |s| {
233    ///     GaussianInteger::from_str(s)
234    ///         .unwrap()
235    ///         .checked_sqrts()
236    ///         .iter()
237    ///         .map(ToString::to_string)
238    ///         .collect::<Vec<_>>()
239    /// };
240    /// assert_eq!(roots("3+4i"), ["-2-i", "2+i"]);
241    /// assert_eq!(roots("-1"), ["-i", "i"]);
242    /// assert_eq!(roots("2+i"), Vec::<String>::new());
243    /// assert_eq!(
244    ///     GaussianInteger::ZERO.checked_sqrts(),
245    ///     [GaussianInteger::ZERO]
246    /// );
247    /// ```
248    pub fn checked_sqrts(&self) -> Vec<Self> {
249        match checked_sqrt_helper(self) {
250            None => Vec::new(),
251            Some(root) if root == 0u32 => vec![root],
252            Some(root) => {
253                let neg_root = -&root;
254                let mut roots = vec![root, neg_root];
255                roots.sort_by(|a, b| {
256                    ComparableGaussianIntegerRef(a).cmp(&ComparableGaussianIntegerRef(b))
257                });
258                roots
259            }
260        }
261    }
262}