Skip to main content

malachite_base/num/factorization/
is_square.rs

1// Copyright © 2026 William Youmans
2//
3// Uses code adopted from the FLINT Library.
4//
5//      Copyright © 2009 William Hart
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::num::arithmetic::traits::{CheckedSqrt, FloorSqrt, Parity, Square};
14use crate::num::basic::integers::PrimitiveInt;
15use crate::num::conversion::traits::{SplitInHalf, WrappingFrom};
16use crate::num::factorization::TWICE_U64_WIDTH;
17use crate::num::factorization::traits::IsSquare;
18
19const IS_SQUARE_MOD64: [bool; 64] = [
20    true, true, false, false, true, false, false, false, false, true, false, false, false, false,
21    false, false, true, true, false, false, false, false, false, false, false, true, false, false,
22    false, false, false, false, false, true, false, false, true, false, false, false, false, true,
23    false, false, false, false, false, false, false, true, false, false, false, false, false,
24    false, false, true, false, false, false, false, false, false,
25];
26
27const IS_SQUARE_MOD65: [bool; 65] = [
28    true, true, false, false, true, false, false, false, false, true, true, false, false, false,
29    true, false, true, false, false, false, false, false, false, false, false, true, true, false,
30    false, true, true, false, false, false, false, true, true, false, false, true, true, false,
31    false, false, false, false, false, false, false, true, false, true, false, false, false, true,
32    true, false, false, false, false, true, false, false, true,
33];
34
35const IS_SQUARE_MOD63: [bool; 63] = [
36    true, true, false, false, true, false, false, true, false, true, false, false, false, false,
37    true, false, true, false, true, false, false, false, true, false, false, true, false, false,
38    true, false, false, false, false, false, false, true, true, true, false, false, false, false,
39    false, true, false, false, true, false, false, true, false, false, false, false, false, false,
40    true, false, true, false, false, false, false,
41];
42
43// This is n_is_square when FLINT64 is false, from ulong_extras/is_square.c, FLINT 3.1.2.
44fn is_square_u64(x: u64) -> bool {
45    IS_SQUARE_MOD64[(x % 64) as usize]
46        && IS_SQUARE_MOD63[(x % 63) as usize]
47        && IS_SQUARE_MOD65[(x % 65) as usize]
48        && x.floor_sqrt().square() == x
49}
50
51macro_rules! impl_unsigned {
52    ($t: ident) => {
53        impl IsSquare for $t {
54            /// Determines whether an integer is a perfect square.
55            ///
56            /// $f(x) = (\exists b \in \Z : b^2 = x)$.
57            ///
58            /// # Worst-case complexity
59            /// Constant time and additional memory.
60            ///
61            /// # Examples
62            /// See [here](super::is_square#is_square).
63            #[inline]
64            fn is_square(&self) -> bool {
65                is_square_u64(u64::wrapping_from(*self))
66            }
67        }
68    };
69}
70impl_unsigned!(u8);
71impl_unsigned!(u16);
72impl_unsigned!(u32);
73impl_unsigned!(u64);
74impl_unsigned!(usize);
75
76// From mpn/generic/mod_34lsub1.c
77const B1: u64 = u64::WIDTH >> 2;
78const B2: u64 = B1 << 1;
79const B3: u64 = B1 * 3;
80
81const M2: u64 = (1 << B2) - 1;
82const M3: u64 = (1 << B3) - 1;
83
84const fn low0(n: u64) -> u64 {
85    n & M3
86}
87
88const fn high0(n: u64) -> u64 {
89    n >> B3
90}
91
92const fn low1(n: u64) -> u64 {
93    (n & M2) << B1
94}
95
96const fn high1(n: u64) -> u64 {
97    n >> B2
98}
99
100// This is mpn_mod_34lsub1 from mpn/generic/mod_34lsub1.c, GMP 6.3.0.
101//
102// Calculate a remainder from `limbs` divided by 2^(u64::WIDTH*3/4)-1. The remainder is not fully
103// reduced, it's any limb value congruent to `limbs` modulo that divisor.
104//
105// Check gen-psqr.c. mpn_mod_34lsub1 preferred over mpn_mod_1 (plus a PERFSQR_PP modulus) with 32
106// and 64 bit limb.
107const fn mod_34lsub1(x_hi: u64, x_lo: u64) -> u64 {
108    low0(x_lo) + high0(x_lo) + low1(x_hi) + high1(x_hi)
109}
110
111const MOD34_BITS: u64 = (u64::WIDTH >> 2) * 3;
112const MOD34_MASK: u64 = (1 << MOD34_BITS) - 1;
113
114const fn perfsqr_mod_34(x_hi: u64, x_lo: u64) -> u64 {
115    let r = mod_34lsub1(x_hi, x_lo);
116    (r & MOD34_MASK) + (r >> MOD34_BITS)
117}
118
119// This is PERFSQR_MOD_BITS from mpn/perfsqr.h, GMP 6.3.0. Either 49 on 64 bit limb or 25 on 32 bit
120// limb. 2^48-1 = 3^2 * 5 * 7 * 13 * 17 * 97 ... 2^24-1 = 3^2 * 5 * 7 * 13 * 17 ...
121const SQR_MOD_BITS: u64 = MOD34_BITS + 1;
122const SQR_MOD_MASK: u64 = (1 << SQR_MOD_BITS) - 1;
123
124const fn perfsqr_mod_idx(r: u64, d: u64, inv: u64) -> u64 {
125    assert!(r <= SQR_MOD_MASK);
126    assert!(inv.wrapping_mul(d) & SQR_MOD_MASK == 1);
127    assert!(u64::MAX / d >= SQR_MOD_MASK);
128    let q = r.wrapping_mul(inv) & SQR_MOD_MASK;
129    assert!(r == (q.wrapping_mul(d) & SQR_MOD_MASK));
130    q.wrapping_mul(d) >> SQR_MOD_BITS
131}
132
133// Single limb. Check precomputed bitmasks to see if remainder is a quadratic residue
134fn perfsqr_mod_1(r: u64, d: u64, inv: u64, mask: u64) -> bool {
135    assert!(d <= u64::WIDTH);
136    let idx = perfsqr_mod_idx(r, d, inv);
137    if (mask >> idx).even() {
138        // non-square
139        return false;
140    }
141    true
142}
143
144// Double limb. Check precomputed bitmasks to see if remainder is a quadratic residue
145fn perfsqr_mod_2(r: u64, d: u64, inv: u64, mhi: u64, mlo: u64) -> bool {
146    assert!(d <= TWICE_U64_WIDTH);
147    let mut idx = perfsqr_mod_idx(r, d, inv);
148    let m = if idx < u64::WIDTH { mlo } else { mhi };
149    idx %= u64::WIDTH;
150    if (m >> idx).even() {
151        // non-square
152        return false;
153    }
154    true
155}
156
157// This test identifies 97.81% as non-squares. Grand total sq_res_0x100 and PERFSQR_MOD_TEST, 99.62%
158// non-squares.
159fn perfsqr_mod_test(x: u128) -> bool {
160    let (x_hi, x_lo) = x.split_in_half();
161    let r = perfsqr_mod_34(x_hi, x_lo);
162    perfsqr_mod_2(r, 91, 0xfd2fd2fd2fd3, 0x2191240, 0x8850a206953820e1) // 69.23%
163        && perfsqr_mod_2(r, 85, 0xfcfcfcfcfcfd, 0x82158, 0x10b48c4b4206a105) // 68.24%
164        && perfsqr_mod_1(r, 9, 0xe38e38e38e39, 0x93)  // 55.56%
165        && perfsqr_mod_2(r, 97, 0xfd5c5f02a3a1, 0x1eb628b47, 0x6067981b8b451b5f) // 49.48%
166}
167
168// This is sq_res0x100 from mpn/perfsqr.h when generated for 64 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).
170const SQR_MOD256: [u64; 4] =
171    [0x202021202030213, 0x202021202020213, 0x202021202030212, 0x202021202020212];
172
173impl IsSquare for u128 {
174    /// Determines whether an integer is a perfect square.
175    ///
176    /// $f(x) = (\exists b \in \Z : b^2 = x)$.
177    ///
178    /// # Worst-case complexity
179    /// $T(n) = O(n)$
180    ///
181    /// $M(n) = O(1)$
182    ///
183    /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`: the
184    /// quadratic-residue prefilters are constant-time and reject most nonsquares, but surviving
185    /// candidates pay one $O(n)$ 128-bit square root.
186    ///
187    /// # Examples
188    /// See [here](super::is_square#is_square).
189    // The body stays separate staged tests, each with its own explanation; merging the first two
190    // into one `if` would obscure the staging.
191    #[cfg_attr(dylint_lib = "malachite_lints", allow(collapse_adjacent_ifs))]
192    #[inline]
193    fn is_square(&self) -> bool {
194        let idx = self % 0x100; // mod 256
195
196        // The first test excludes 212/256 (82.8%) of the perfect square candidates in O(1) time.
197        //
198        // This just checks the particular bit in the bitmask SQR_MOD256_U64 encoding where the
199        // input can be a perfect square mod 256.
200        if (SQR_MOD256[(idx >> u64::LOG_WIDTH) as usize]
201            >> (idx & const { u64::WIDTH_MASK as Self }))
202        .even()
203        {
204            return false;
205        }
206        // The second test uses mpn_mod_34lsub1 to detect non-squares according to their residues
207        // modulo small primes (or powers of primes). See mpn/perfsqr.h, GMP 6.3.0.
208        if !perfsqr_mod_test(*self) {
209            return false;
210        }
211        // For the third and last test, we finally compute the square root, to make sure we've
212        // really got a perfect square.
213        self.checked_sqrt().is_some()
214    }
215}
216
217macro_rules! impl_signed {
218    ($t: ident) => {
219        impl IsSquare for $t {
220            /// Determines whether an integer is a perfect square.
221            ///
222            /// $f(x) = (\exists b \in \Z : b^2 = x)$.
223            ///
224            /// # Worst-case complexity
225            /// Constant time and additional memory.
226            ///
227            /// # Examples
228            /// See [here](super::is_square#is_square).
229            #[inline]
230            fn is_square(&self) -> bool {
231                if *self < 0 {
232                    false
233                } else {
234                    self.unsigned_abs().is_square()
235                }
236            }
237        }
238    };
239}
240apply_to_signeds!(impl_signed);