Skip to main content

malachite_nz/natural/factorization/
is_square.rs

1// Copyright © 2026 William Youmans
2//
3// Uses code adopted from the GMP Library.
4//
5// This file is part of Malachite.
6//
7// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
8// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
9// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
10
11use crate::natural::InnerNatural::{Large, Small};
12use crate::natural::Natural;
13use crate::natural::arithmetic::sqrt::limbs_checked_sqrt;
14use crate::platform::Limb;
15use malachite_base::num::basic::integers::PrimitiveInt;
16use malachite_base::num::basic::traits::Zero;
17use malachite_base::num::conversion::traits::ExactFrom;
18use malachite_base::num::factorization::traits::IsSquare;
19
20const MOD34_BITS: Limb = ((Limb::WIDTH as Limb) / 4) * 3;
21const MOD34_MASK: Limb = (1 << MOD34_BITS) - 1;
22
23// This is PERFSQR_MOD_BITS from mpn/perfsqr.h, GMP 6.3.0. Either 49 on 64 bit limb or 25 on 32 bit
24// limb. 2^48-1 = 3^2 * 5 * 7 * 13 * 17 * 97 ... 2^24-1 = 3^2 * 5 * 7 * 13 * 17 ...
25const SQR_MOD_BITS: Limb = MOD34_BITS + 1;
26const SQR_MOD_MASK: Limb = (1 << SQR_MOD_BITS) - 1;
27
28// From mpn/generic/mod_34lsub1.c
29const B1: Limb = (Limb::WIDTH as Limb) / 4;
30const B2: Limb = B1 * 2;
31const B3: Limb = B1 * 3;
32
33const M1: Limb = (1 << B1) - 1;
34const M2: Limb = (1 << B2) - 1;
35const M3: Limb = (1 << B3) - 1;
36
37const fn low0(n: Limb) -> Limb {
38    n & M3
39}
40const fn high0(n: Limb) -> Limb {
41    n >> B3
42}
43
44const fn low1(n: Limb) -> Limb {
45    (n & M2) << B1
46}
47const fn high1(n: Limb) -> Limb {
48    n >> B2
49}
50
51const fn low2(n: Limb) -> Limb {
52    (n & M1) << B2
53}
54const fn high2(n: Limb) -> Limb {
55    n >> B1
56}
57
58const fn parts0(n: Limb) -> Limb {
59    low0(n) + high0(n)
60}
61const fn parts1(n: Limb) -> Limb {
62    low1(n) + high1(n)
63}
64const fn parts2(n: Limb) -> Limb {
65    low2(n) + high2(n)
66}
67
68// This is mpn_mod_34lsub1 from mpn/generic/mod_34lsub1.c, GMP 6.3.0.
69//
70// Calculate a remainder from `limbs` divided by 2^(Limb::WIDTH*3/4)-1. The remainder is not fully
71// reduced, it's any limb value congruent to `limbs` modulo that divisor.
72//
73// Check gen-psqr.c. mpn_mod_34lsub1 preferred over mpn_mod_1 (plus a PERFSQR_PP modulus) with 32
74// and 64 bit limb.
75pub(crate) fn mod_34lsub1(limbs: &[Limb]) -> Limb {
76    // Process in chunks of 3, chunks lets us cleanly handle remainder
77    let (sums, carries) = limbs.chunks(3).fold(
78        ([Limb::ZERO; 3], [Limb::ZERO; 3]),
79        |(mut sums, mut carries), chunk| {
80            for (i, &limb) in chunk.iter().enumerate() {
81                let (sum, overflow) = sums[i].overflowing_add(limb);
82                sums[i] = sum;
83                if overflow {
84                    carries[i] += 1;
85                }
86            }
87            (sums, carries)
88        },
89    );
90    parts0(sums[0])
91        + parts1(sums[1])
92        + parts2(sums[2])
93        + parts1(carries[0])
94        + parts2(carries[1])
95        + parts0(carries[2])
96}
97
98// reduce mod 2^(WIDTH*3/4) - 1 and ensure result is in [0, modulus)
99fn perfsqr_mod_34(limbs: &[Limb]) -> Limb {
100    let r = mod_34lsub1(limbs);
101    (r & MOD34_MASK) + (r >> MOD34_BITS)
102}
103
104const fn perfsqr_mod_idx(r: Limb, d: Limb, inv: Limb) -> Limb {
105    assert!(r <= SQR_MOD_MASK);
106    assert!(inv.wrapping_mul(d) & SQR_MOD_MASK == 1);
107    assert!(Limb::MAX / d >= SQR_MOD_MASK);
108    let q = r.wrapping_mul(inv) & SQR_MOD_MASK;
109    assert!(r == (q.wrapping_mul(d) & SQR_MOD_MASK));
110    q.wrapping_mul(d) >> SQR_MOD_BITS
111}
112
113// Single limb. Check precomputed bitmasks to see if remainder is a quadratic residue
114fn perfsqr_mod_1(r: Limb, d: Limb, inv: Limb, mask: Limb) -> bool {
115    //   CNST_LIMB(0x202021202020213),
116    assert!(d <= Limb::WIDTH as Limb);
117    let idx = perfsqr_mod_idx(r, d, inv);
118    if (mask >> idx) & 1 == 0 {
119        // non-square
120        return false;
121    }
122    true
123}
124
125// Double limb. Check precomputed bitmasks to see if remainder is a quadratic residue
126fn perfsqr_mod_2(r: Limb, d: Limb, inv: Limb, mhi: Limb, mlo: Limb) -> bool {
127    assert!(d <= 2 * Limb::WIDTH as Limb);
128    let mut idx = perfsqr_mod_idx(r, d, inv);
129    let m = if idx < Limb::WIDTH as Limb { mlo } else { mhi };
130    idx %= Limb::WIDTH as Limb;
131    if (m >> idx) & 1 == 0 {
132        // non-square
133        return false;
134    }
135    true
136}
137
138// This test identifies 97.81% as non-squares. Grand total sq_res_0x100 and PERFSQR_MOD_TEST, 99.62%
139// non-squares.
140#[cfg(not(feature = "32_bit_limbs"))]
141fn perfsqr_mod_test(limbs: &[Limb]) -> bool {
142    let r = perfsqr_mod_34(limbs);
143
144    perfsqr_mod_2(r, 91, 0xfd2fd2fd2fd3, 0x2191240, 0x8850a206953820e1) // 69.23%
145        && perfsqr_mod_2(r, 85, 0xfcfcfcfcfcfd, 0x82158, 0x10b48c4b4206a105) // 68.24%
146        && perfsqr_mod_1(r, 9, 0xe38e38e38e39, 0x93)  // 55.56%
147        && perfsqr_mod_2(r, 97, 0xfd5c5f02a3a1, 0x1eb628b47, 0x6067981b8b451b5f) // 49.48%
148}
149
150// This test identifies 95.66% as non-squares. Grand total sq_res_0x100 and PERFSQR_MOD_TEST, 99.25%
151// non-squares.
152#[cfg(feature = "32_bit_limbs")]
153fn perfsqr_mod_test(limbs: &[Limb]) -> bool {
154    let r = perfsqr_mod_34(limbs);
155
156    perfsqr_mod_2(r, 45, 0xfa4fa5, 0x920, 0x1a442481) // 73.33%
157        && perfsqr_mod_1(r, 17, 0xf0f0f1, 0x1a317) // 47.06 %
158        && perfsqr_mod_1(r, 13, 0xec4ec5, 0x9e5) // 46.15 %
159        && perfsqr_mod_1(r, 7, 0xdb6db7, 0x69) // 42.86 %
160}
161
162// This is sq_res0x100 from mpn/perfsqr.h when generated for 64 bit limb, GMP 6.3.0. Non-zero bit
163// indicates a quadratic residue mod 0x100. This test identifies 82.81% as non-squares (212/256).
164#[cfg(not(feature = "32_bit_limbs"))]
165const SQR_MOD256: [u64; 4] =
166    [0x202021202030213, 0x202021202020213, 0x202021202030212, 0x202021202020212];
167
168// This is sq_res0x100 from mpn/perfsqr.h when generated for 32 bit limb, GMP 6.3.0. Non-zero bit
169// indicates a quadratic residue mod 0x100. This test identifies 82.81% as non-squares (212/256).
170#[cfg(feature = "32_bit_limbs")]
171const SQR_MOD256: [u32; 8] =
172    [0x2030213, 0x2020212, 0x2020213, 0x2020212, 0x2030212, 0x2020212, 0x2020212, 0x2020212];
173
174fn limbs_is_square(limbs: &[Limb]) -> bool {
175    assert!(!limbs.is_empty());
176    let idx = limbs[0] % 0x100; // mod 256
177
178    // The first test excludes 212/256 (82.8%) of the perfect square candidates in O(1) time.
179    //
180    // This just checks the particular bit in the bitmask SQR_MOD256_U64 encoding where the input
181    // can be a perfect square mod 256.
182    if (SQR_MOD256[usize::exact_from(idx >> Limb::LOG_WIDTH)]
183        >> (idx & const { Limb::WIDTH_MASK as Limb }))
184        & 1
185        == 0
186    {
187        return false;
188    }
189    // The second test uses mpn_mod_34lsub1 to detect non-squares according to their residues modulo
190    // small primes (or powers of primes). See mpn/perfsqr.h, GMP 6.3.0.
191    if !perfsqr_mod_test(limbs) {
192        return false;
193    }
194    // For the third and last test, we finally compute the square root, to make sure we've really
195    // got a perfect square.
196    limbs_checked_sqrt(limbs).is_some()
197}
198
199impl IsSquare for Natural {
200    /// Determine whether a [`Natural`] is a perfect square. Rules out > 99% of non-squares in
201    /// $O(1)$ time using quadratic residue tests, then falls back to computing the square root. The
202    /// [`Natural`] is taken by reference.
203    ///
204    /// $f(x) = (\exists b \in \Z : b^2 = x)$.
205    ///
206    /// # Worst-case complexity
207    /// $T(n) = O(n \log n \log\log n)$
208    ///
209    /// $M(n) = O(n \log n)$
210    ///
211    /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
212    /// # Examples
213    /// ```
214    /// use malachite_base::num::factorization::traits::IsSquare;
215    /// use malachite_nz::natural::Natural;
216    ///
217    /// let x = Natural::from(12345u64);
218    /// let mut y = &x * &x;
219    ///
220    /// assert!((&y).is_square());
221    ///
222    /// y += Natural::from(1u64);
223    /// assert!(!(&y).is_square());
224    /// ```
225    fn is_square(&self) -> bool {
226        match self {
227            // use the FLINT n_is_square impl for primitive integers TODO: is the FLINT n_is_square
228            // better than the GMP algorithm in this file for word size integers?
229            Self(Small(small)) => small.is_square(),
230            Self(Large(limbs)) => limbs_is_square(limbs),
231        }
232    }
233}