Skip to content

Commit

Permalink
Merge pull request mpusz#640 from chiphogg/chiphogg/strong-lucas#509
Browse files Browse the repository at this point in the history
Add the Strong Lucas Probable Prime test
  • Loading branch information
mpusz authored Nov 15, 2024
2 parents c901b73 + 82040c1 commit ec91498
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 1 deletion.
121 changes: 120 additions & 1 deletion src/core/include/mp-units/ext/prime.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ namespace mp_units::detail {
return add_mod(
// Transform into "negative space" to make the first parameter as small as possible;
// then, transform back.
n - mul_mod(n % a, num_batches, n),
(n - mul_mod(n % a, num_batches, n)) % n,

// Handle the leftover product (which is guaranteed to fit in the integer type).
(a * (b % batch_size)) % n,
Expand Down Expand Up @@ -260,6 +260,125 @@ struct NumberDecomposition {
}
}

// The "D" parameter in the Strong Lucas Probable Prime test.
//
// Default construction produces the first value to try, according to Selfridge's parameter selection.
// Calling `successor()` on this repeatedly will produce the sequence of values to try.
struct LucasDParameter {
std::uint64_t mag = 5u;
bool pos = true;

friend constexpr int as_int(LucasDParameter p)
{
int D = static_cast<int>(p.mag);
return p.pos ? D : -D;
}
friend constexpr LucasDParameter successor(LucasDParameter p) { return {.mag = p.mag + 2u, .pos = !p.pos}; }
};

// The first `D` in the infinite sequence {5, -7, 9, -11, ...} whose Jacobi symbol is -1. This is the D we
// want to use for the Strong Lucas Probable Prime test.
//
// Precondition: `n` is not a perfect square.
[[nodiscard]] consteval LucasDParameter find_first_D_with_jacobi_symbol_neg_one(std::uint64_t n)
{
LucasDParameter D{};
while (jacobi_symbol(as_int(D), n) != -1) {
D = successor(D);
}
return D;
}

// An element of the Lucas sequence, with parameters tuned for the Strong Lucas test.
//
// The default values give the first element (i.e., k=1) of the sequence.
struct LucasSequenceElement {
std::uint64_t u = 1u;
std::uint64_t v = 1u;
};

// Produce the Lucas element whose index is twice the input element's index.
[[nodiscard]] consteval LucasSequenceElement double_strong_lucas_index(const LucasSequenceElement& element,
std::uint64_t n, LucasDParameter D)
{
const auto& [u, v] = element;

std::uint64_t v_squared = mul_mod(v, v, n);
std::uint64_t D_u_squared = mul_mod(D.mag % n, mul_mod(u, u, n), n);
std::uint64_t new_v = D.pos ? add_mod(v_squared, D_u_squared, n) : sub_mod(v_squared, D_u_squared, n);
new_v = half_mod_odd(new_v, n);

return {.u = mul_mod(u, v, n), .v = new_v};
}

[[nodiscard]] consteval LucasSequenceElement increment_strong_lucas_index(const LucasSequenceElement& element,
std::uint64_t n, LucasDParameter D)
{
const auto& [u, v] = element;

const auto new_u = half_mod_odd(add_mod(u, v, n), n);

const auto D_u = mul_mod(D.mag % n, u, n);
auto new_v = D.pos ? add_mod(v, D_u, n) : sub_mod(v, D_u, n);
new_v = half_mod_odd(new_v, n);

return {.u = new_u, .v = new_v};
}

[[nodiscard]] consteval LucasSequenceElement find_strong_lucas_element(std::uint64_t i, std::uint64_t n,
LucasDParameter D)
{
LucasSequenceElement element{};

bool bits[64] = {};
std::size_t n_bits = 0u;
while (i > 1u) {
bits[n_bits++] = (i & 1u);
i >>= 1u;
}

for (auto j = n_bits; j > 0u; --j) {
element = double_strong_lucas_index(element, n, D);
if (bits[j - 1u]) {
element = increment_strong_lucas_index(element, n, D);
}
}

return element;
}

// Perform a Strong Lucas Probable Prime test on `n`.
//
// Precondition: (n >= 2).
// Precondition: (n is odd).
[[nodiscard]] consteval bool strong_lucas_probable_prime(std::uint64_t n)
{
MP_UNITS_EXPECTS_DEBUG(n >= 2u);
MP_UNITS_EXPECTS_DEBUG(n % 2u == 1u);

if (is_perfect_square(n)) {
return false;
}

const auto D = find_first_D_with_jacobi_symbol_neg_one(n);

const auto [s, d] = decompose(n + 1u);

auto element = find_strong_lucas_element(d, n, D);
if (element.u == 0u) {
return true;
}

for (auto i = 0u; i < s; ++i) {
if (element.v == 0u) {
return true;
}
element = double_strong_lucas_index(element, n, D);
}

return false;
}

[[nodiscard]] consteval bool is_prime_by_trial_division(std::uintmax_t n)
{
for (std::uintmax_t f = 2; f * f <= n; f += 1 + (f % 2)) {
Expand Down
32 changes: 32 additions & 0 deletions test/static/prime_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,36 @@ static_assert(!is_perfect_square(BIG_SQUARE - 1u));
static_assert(is_perfect_square(BIG_SQUARE));
static_assert(!is_perfect_square(BIG_SQUARE + 1u));

// Tests for the Strong Lucas Probable Prime test.
static_assert(as_int(LucasDParameter{.mag = 5, .pos = true}) == 5);
static_assert(as_int(LucasDParameter{.mag = 7, .pos = false}) == -7);

static_assert(as_int(LucasDParameter{}) == 5, "First D parameter in the sequence is 5");
static_assert(as_int(successor(LucasDParameter{})) == -7, "Incrementing adds 2 to the mag, and flips the sign");
static_assert(as_int(successor(successor(LucasDParameter{}))) == 9);
static_assert(as_int(successor(successor(successor(LucasDParameter{})))) == -11);

static_assert(strong_lucas_probable_prime(3u), "Known small prime");
static_assert(strong_lucas_probable_prime(5u), "Known small prime");
static_assert(strong_lucas_probable_prime(7u), "Known small prime");
static_assert(!strong_lucas_probable_prime(9u), "Known small composite");

// Test some Miller-Rabin pseudoprimes (https://oeis.org/A001262), which should NOT be marked prime.
static_assert(!strong_lucas_probable_prime(2047u), "Miller-Rabin pseudoprime");
static_assert(!strong_lucas_probable_prime(3277u), "Miller-Rabin pseudoprime");
static_assert(!strong_lucas_probable_prime(486737u), "Miller-Rabin pseudoprime");

// Test some Strong Lucas pseudoprimes (https://oeis.org/A217255).
static_assert(strong_lucas_probable_prime(5459u), "Strong Lucas pseudoprime");
static_assert(strong_lucas_probable_prime(5777u), "Strong Lucas pseudoprime");
static_assert(strong_lucas_probable_prime(10877u), "Strong Lucas pseudoprime");
static_assert(strong_lucas_probable_prime(324899u), "Strong Lucas pseudoprime");

// Test some actual primes
static_assert(strong_lucas_probable_prime(225'653'407'801u), "Large known prime");
static_assert(strong_lucas_probable_prime(334'524'384'739u), "Large known prime");
static_assert(strong_lucas_probable_prime(9'007'199'254'740'881u), "Large known prime");

static_assert(strong_lucas_probable_prime(18'446'744'073'709'551'557u), "Largest 64-bit prime");

} // namespace

0 comments on commit ec91498

Please sign in to comment.