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}