Skip to main content

malachite_nz/gaussian_integer/arithmetic/
root.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::{
14    CanonicalizeUnit, CheckedRoot, CheckedSqrt, ContentAndPrimitivePart, DivRem, Gcd, ModPowerOf2,
15    MulIPow, MulIPowAssign, Parity, Pow, PowerOf2, Square,
16};
17use malachite_base::num::basic::traits::Zero;
18use malachite_base::num::conversion::traits::ExactFrom;
19use malachite_base::num::logic::traits::TrailingZeros;
20
21fn norm(z: &GaussianInteger) -> Natural {
22    z.real.unsigned_abs_ref().square() + z.imaginary.unsigned_abs_ref().square()
23}
24
25// The unique kth root of a nonzero z for odd k >= 3, if it exists.
26//
27// Strip the prime 1 + i, whose multiplicity must be a multiple of k, and split what remains into
28// its content C and primitive part Q. If z = w^k, then C is the kth power of the content of the
29// corresponding part of w, a Natural root check, and Q is a unit times P^k for the primitive part P
30// of w. A primitive Gaussian integer with no factor 1 + i has, for each pair of conjugate split
31// primes, only one of the two, so gcd(Q, N(P)) = gcd(u P^k, P conj(P)) is P up to a unit.
32// Reassembling gives w up to a unit, and the unit is fixed by comparing the kth power with z: for
33// odd k the map from units to their kth powers is a bijection.
34fn odd_root(z: &GaussianInteger, k: u64) -> Option<GaussianInteger> {
35    let (stripped, one_plus_i_exp) = z.remove_one_plus_i();
36    let (w_one_plus_i_exp, r) = one_plus_i_exp.div_rem(k);
37    if r != 0 {
38        return None;
39    }
40    let (content, primitive) = stripped.content_and_primitive_part();
41    let w_content = Integer::from(content.checked_root(k)?);
42    let primitive_norm = norm(&primitive).checked_root(k)?;
43    let w_primitive = (&primitive).gcd(GaussianInteger::from(Integer::from(primitive_norm)));
44    let mut w = GaussianInteger {
45        real: w_primitive.real * &w_content,
46        imaginary: w_primitive.imaginary * w_content,
47    };
48    // multiply by (1 + i)^e = (2i)^(e / 2) (1 + i)^(e mod 2)
49    let half = w_one_plus_i_exp >> 1;
50    w <<= half;
51    w.mul_i_pow_assign(half);
52    if w_one_plus_i_exp.odd() {
53        // (a + bi)(1 + i) = (a - b) + (a + b)i
54        let sum = &w.real + &w.imaginary;
55        w.real -= &w.imaginary;
56        w.imaginary = sum;
57    }
58    // z = (i^j w)^k = i^(jk) w^k, so w^k = i^(-jk) z; k is odd, so k is its own inverse mod 4
59    let w_pow = (&w).pow(k);
60    let j = (0..4).find(|&j| (&w_pow).mul_i_pow(j) == *z)?;
61    Some(w.mul_i_pow((j * k).mod_power_of_2(2)))
62}
63
64// The principal exp-th root, as described in the documentation of `checked_root`.
65fn principal_root(z: &GaussianInteger, exp: u64) -> Option<GaussianInteger> {
66    assert_ne!(exp, 0, "Cannot take the 0th root of a Gaussian integer");
67    if *z == 0u32 {
68        return Some(GaussianInteger::ZERO);
69    } else if exp == 1 {
70        return Some(z.clone());
71    }
72    let e = TrailingZeros::trailing_zeros(exp);
73    let m = exp >> e;
74    // The 2^e-th roots of the odd-part root are rotations of one another, but a rotation of a
75    // square need not be a square (i is not a unit square), so following one chain of principal
76    // square roots can dead-end where another succeeds: 16 = (1+i)^8, yet 16, 4, 2 stops at 2 while
77    // 16, -4, 2i, 1+i reaches the root. So every square root is kept; the candidate set never
78    // exceeds four elements.
79    let mut candidates = vec![if m == 1 { z.clone() } else { odd_root(z, m)? }];
80    for _ in 0..e {
81        candidates = candidates
82            .into_iter()
83            .filter_map(CheckedSqrt::checked_sqrt)
84            .flat_map(|w| [-&w, w])
85            .collect();
86        if candidates.is_empty() {
87            return None;
88        }
89    }
90    let candidate = candidates.pop()?;
91    Some(match e {
92        0 => candidate,
93        1 => {
94            if (&candidate.real, &candidate.imaginary) > (&Integer::ZERO, &Integer::ZERO) {
95                candidate
96            } else {
97                -candidate
98            }
99        }
100        _ => candidate.canonicalize_unit(),
101    })
102}
103
104impl CheckedRoot<u64> for GaussianInteger {
105    type Output = Self;
106
107    /// Returns the principal $n$th root of a [`GaussianInteger`], or `None` if it is not a perfect
108    /// $n$th power. The [`GaussianInteger`] is taken by value.
109    ///
110    /// A nonzero Gaussian integer has either no $n$th roots or exactly $\gcd(n, 4)$ of them: if $w$
111    /// is one, the others are $w\zeta$ for the units $\zeta$ with $\zeta^n = 1$. The one returned
112    /// is the principal root, whose argument lies in $(-\pi/g, \pi/g]$ for $g = \gcd(n, 4)$: the
113    /// unique root for odd $n$, the root with positive real part (or zero real part and positive
114    /// imaginary part) for $n \equiv 2 \pmod 4$, and the root in canonical unit form for $4 \mid
115    /// n$.
116    ///
117    /// Writing $n = 2^e m$ with $m$ odd, the unique $m$th root is found exactly through the norm:
118    /// with $N = N(z)^{1/m}$ and $d = \gcd(z, N)$, the quotient $N d / \bar{d}$ is the square of
119    /// the root up to a unit, and the unit is fixed by raising to the $m$th power. Square roots are
120    /// then taken $e$ times over the candidate set, which never exceeds four roots.
121    ///
122    /// $$
123    /// f(z, n) = \begin{cases}
124    ///     \operatorname{Some}(\sqrt\[n\]{z}) & \text{if} \quad \sqrt\[n\]{z} \in \Z\[i\], \\\\
125    ///     \operatorname{None} & \textrm{otherwise}.
126    /// \end{cases}
127    /// $$
128    ///
129    /// # Worst-case complexity
130    /// $T(n) = O(n^2)$
131    ///
132    /// $M(n) = O(n)$
133    ///
134    /// where $T$ is time, $M$ is additional memory, and $n$ is the maximum number of significant
135    /// bits of the real and imaginary parts of `self`.
136    ///
137    /// # Panics
138    /// Panics if `exp` is zero.
139    ///
140    /// # Examples
141    /// ```
142    /// use malachite_base::num::arithmetic::traits::CheckedRoot;
143    /// use malachite_nz::gaussian_integer::GaussianInteger;
144    /// use std::str::FromStr;
145    ///
146    /// let root = |s, exp| {
147    ///     GaussianInteger::from_str(s)
148    ///         .unwrap()
149    ///         .checked_root(exp)
150    ///         .map(|r| r.to_string())
151    /// };
152    /// // (2+i)^5 = -38+41i
153    /// assert_eq!(root("-38+41i", 5), Some("2+i".to_string()));
154    /// // -4 = (1+i)^4, and 1+i is the principal root of the four
155    /// assert_eq!(root("-4", 4), Some("1+i".to_string()));
156    /// // the unique cube root of -8 is -2
157    /// assert_eq!(root("-8", 3), Some("-2".to_string()));
158    /// assert_eq!(root("3+4i", 3), None);
159    /// ```
160    #[inline]
161    fn checked_root(self, exp: u64) -> Option<Self> {
162        principal_root(&self, exp)
163    }
164}
165
166impl CheckedRoot<u64> for &GaussianInteger {
167    type Output = GaussianInteger;
168
169    /// Returns the principal $n$th root of a [`GaussianInteger`], or `None` if it is not a perfect
170    /// $n$th power. The [`GaussianInteger`] is taken by reference.
171    ///
172    /// A nonzero Gaussian integer has either no $n$th roots or exactly $\gcd(n, 4)$ of them: if $w$
173    /// is one, the others are $w\zeta$ for the units $\zeta$ with $\zeta^n = 1$. The one returned
174    /// is the principal root, whose argument lies in $(-\pi/g, \pi/g]$ for $g = \gcd(n, 4)$: the
175    /// unique root for odd $n$, the root with positive real part (or zero real part and positive
176    /// imaginary part) for $n \equiv 2 \pmod 4$, and the root in canonical unit form for $4 \mid
177    /// n$.
178    ///
179    /// Writing $n = 2^e m$ with $m$ odd, the unique $m$th root is found exactly through the norm:
180    /// with $N = N(z)^{1/m}$ and $d = \gcd(z, N)$, the quotient $N d / \bar{d}$ is the square of
181    /// the root up to a unit, and the unit is fixed by raising to the $m$th power. Square roots are
182    /// then taken $e$ times over the candidate set, which never exceeds four roots.
183    ///
184    /// $$
185    /// f(z, n) = \begin{cases}
186    ///     \operatorname{Some}(\sqrt\[n\]{z}) & \text{if} \quad \sqrt\[n\]{z} \in \Z\[i\], \\\\
187    ///     \operatorname{None} & \textrm{otherwise}.
188    /// \end{cases}
189    /// $$
190    ///
191    /// # Worst-case complexity
192    /// $T(n) = O(n^2)$
193    ///
194    /// $M(n) = O(n)$
195    ///
196    /// where $T$ is time, $M$ is additional memory, and $n$ is the maximum number of significant
197    /// bits of the real and imaginary parts of `self`.
198    ///
199    /// # Panics
200    /// Panics if `exp` is zero.
201    ///
202    /// # Examples
203    /// ```
204    /// use malachite_base::num::arithmetic::traits::CheckedRoot;
205    /// use malachite_nz::gaussian_integer::GaussianInteger;
206    /// use std::str::FromStr;
207    ///
208    /// let root = |s, exp| {
209    ///     (&GaussianInteger::from_str(s).unwrap())
210    ///         .checked_root(exp)
211    ///         .map(|r| r.to_string())
212    /// };
213    /// // (2+i)^5 = -38+41i
214    /// assert_eq!(root("-38+41i", 5), Some("2+i".to_string()));
215    /// // -4 = (1+i)^4, and 1+i is the principal root of the four
216    /// assert_eq!(root("-4", 4), Some("1+i".to_string()));
217    /// // the unique cube root of -8 is -2
218    /// assert_eq!(root("-8", 3), Some("-2".to_string()));
219    /// assert_eq!(root("3+4i", 3), None);
220    /// ```
221    #[inline]
222    fn checked_root(self, exp: u64) -> Option<GaussianInteger> {
223        principal_root(self, exp)
224    }
225}
226
227impl GaussianInteger {
228    /// Returns all the $n$th roots of a [`GaussianInteger`]: none if it is not a perfect $n$th
229    /// power, one if it is zero, and otherwise $\gcd(n, 4)$ of them, in the canonical order of
230    /// [`ComparableGaussianInteger`](crate::gaussian_integer::ComparableGaussianInteger),
231    /// lexicographic by real part and then imaginary part.
232    ///
233    /// The principal root is the one whose argument lies in $(-\pi/g, \pi/g]$ for $g = \gcd(n, 4)$;
234    /// see [`CheckedRoot`].
235    ///
236    /// $$
237    /// f(z, n) = \\{ w \in \Z\[i\] : w^n = z \\}.
238    /// $$
239    ///
240    /// # Worst-case complexity
241    /// $T(n) = O(n^2)$
242    ///
243    /// $M(n) = O(n)$
244    ///
245    /// where $T$ is time, $M$ is additional memory, and $n$ is the maximum number of significant
246    /// bits of the real and imaginary parts of `self`.
247    ///
248    /// # Panics
249    /// Panics if `exp` is zero.
250    ///
251    /// # Examples
252    /// ```
253    /// use malachite_base::num::basic::traits::Zero;
254    /// use malachite_nz::gaussian_integer::GaussianInteger;
255    /// use std::str::FromStr;
256    ///
257    /// let roots = |s, exp| {
258    ///     GaussianInteger::from_str(s)
259    ///         .unwrap()
260    ///         .checked_roots(exp)
261    ///         .iter()
262    ///         .map(ToString::to_string)
263    ///         .collect::<Vec<_>>()
264    /// };
265    /// assert_eq!(roots("-4", 4), ["-1-i", "-1+i", "1-i", "1+i"]);
266    /// assert_eq!(roots("-4", 2), ["-2i", "2i"]);
267    /// assert_eq!(roots("-8", 3), ["-2"]);
268    /// assert_eq!(roots("3+4i", 3), Vec::<String>::new());
269    /// assert_eq!(
270    ///     GaussianInteger::ZERO.checked_roots(7),
271    ///     [GaussianInteger::ZERO]
272    /// );
273    /// ```
274    pub fn checked_roots(&self, exp: u64) -> Vec<Self> {
275        let Some(principal) = principal_root(self, exp) else {
276            return Vec::new();
277        };
278        if principal == 0u32 {
279            return vec![principal];
280        }
281        // g = gcd(exp, 4) roots, each the previous one rotated by 2 pi / g, then sorted
282        let g = u64::power_of_2(TrailingZeros::trailing_zeros(exp).min(2));
283        let step = 4 / g;
284        let mut roots = Vec::with_capacity(usize::exact_from(g));
285        let mut root = principal;
286        for _ in 1..g {
287            let next = (&root).mul_i_pow(step);
288            roots.push(root);
289            root = next;
290        }
291        roots.push(root);
292        roots.sort_by(|a, b| ComparableGaussianIntegerRef(a).cmp(&ComparableGaussianIntegerRef(b)));
293        roots
294    }
295}