Add Miller-Rabin probable prime test

This can mark a number as either "probably prime", or "definitely
composite".  The first parameter is the base, and the second is the
number to test.

Future PRs will build up the Strong Lucas test which complements this,
and then form the Baillie-PSW test by composing the two.

Helps #506.
This commit is contained in:
Chip Hogg
2024-11-13 19:56:05 -05:00
parent e4bccad75c
commit cfd9ddb675
2 changed files with 63 additions and 0 deletions

View File

@ -138,6 +138,51 @@ namespace mp_units::detail {
return result;
}
// Decompose a positive integer by factoring out all powers of 2.
struct NumberDecomposition {
std::uint64_t power_of_two;
std::uint64_t odd_remainder;
};
// Express any positive `n` as `(2^s * d)`, where `d` is odd.
[[nodiscard]] consteval NumberDecomposition decompose(std::uint64_t n)
{
NumberDecomposition result{0u, n};
while (result.odd_remainder % 2u == 0u) {
result.odd_remainder /= 2u;
++result.power_of_two;
}
return result;
}
// Perform a Miller-Rabin primality test on `n` using base `a`.
//
// Precondition: (a >= 2).
// Precondition: (n >= a + 2).
// Precondition: (n % 2 == 1).
[[nodiscard]] consteval bool miller_rabin_probable_prime(std::uint64_t a, std::uint64_t n)
{
MP_UNITS_EXPECTS_DEBUG(a >= 2u);
MP_UNITS_EXPECTS_DEBUG(n >= a + 2u);
MP_UNITS_EXPECTS_DEBUG(n % 2u == 1u);
const auto [s, d] = decompose(n - 1u);
auto x = pow_mod(a, d, n);
if (x == 1u) {
return true;
}
const auto minus_one = n - 1u;
for (auto r = 0u; r < s; ++r) {
if (x == minus_one) {
return true;
}
x = mul_mod(x, x, n);
}
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)) {

View File

@ -104,4 +104,22 @@ static_assert(half_mod_odd(MAX_U64 - 2u, MAX_U64) == MAX_U64 - 1u);
static_assert(pow_mod(5u, 8u, 9u) == ((5u * 5u * 5u * 5u) * (5u * 5u * 5u * 5u)) % 9u);
static_assert(pow_mod(2u, 64u, MAX_U64) == 1u);
// Miller-Rabin primality testing.
static_assert(miller_rabin_probable_prime(2u, 5u));
static_assert(miller_rabin_probable_prime(2u, 7u));
static_assert(!miller_rabin_probable_prime(2u, 9u));
static_assert(miller_rabin_probable_prime(2u, 11u));
static_assert(miller_rabin_probable_prime(2u, 2047u), "Known base 2 pseudoprime");
static_assert(miller_rabin_probable_prime(2u, 3277u), "Known base 2 pseudoprime");
static_assert(miller_rabin_probable_prime(3u, 121u), "Known base 3 pseudoprime");
static_assert(miller_rabin_probable_prime(3u, 703u), "Known base 3 pseudoprime");
static_assert(miller_rabin_probable_prime(2u, 225'653'407'801u), "Large known prime");
static_assert(miller_rabin_probable_prime(2u, 334'524'384'739u), "Large known prime");
static_assert(miller_rabin_probable_prime(2u, 9'007'199'254'740'881u), "Large known prime");
static_assert(miller_rabin_probable_prime(2u, 18'446'744'073'709'551'557u), "Largest 64-bit prime");
} // namespace