Skip to content

Commit 0392f13

Browse files
Use Montgomery multiplication in isprime()
1 parent 651f262 commit 0392f13

3 files changed

Lines changed: 103 additions & 34 deletions

File tree

‎Doc/whatsnew/3.16.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ math.integer
284284

285285
* Added :func:`math.integer.isprime` for primality testing of integers
286286
less than 2**64.
287-
(Contributed by Serhiy Storchaka in :gh:`153222`.)
287+
(Contributed by Serhiy Storchaka and Stan Ulbrych in :gh:`153222`.)
288288

289289

290290
os

‎Lib/test/test_math_integer.py‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ def test_isprime_carmichael(self):
478478

479479
def test_isprime_strong_pseudoprimes_base_2(self):
480480
# Composites that pass the strong probable prime test to base 2
481-
# (A001262); they must be caught by the strong Lucas test.
481+
# (A001262); they must be caught by the other bases.
482482
isprime = self.module.isprime
483483
for n in [2047, 3277, 4033, 4681, 8321, 15841, 29341, 42799, 49141,
484484
52633, 65281, 74665, 80581, 85489, 88357, 90751,
@@ -487,8 +487,8 @@ def test_isprime_strong_pseudoprimes_base_2(self):
487487
self.assertIs(isprime(n), False)
488488

489489
def test_isprime_strong_lucas_pseudoprimes(self):
490-
# Composites that pass the strong Lucas test with Selfridge's
491-
# parameters (A217255).
490+
# Composites that are strong Lucas pseudoprimes (A217255); they
491+
# are unrelated to the Miller-Rabin bases and must still fail.
492492
isprime = self.module.isprime
493493
for n in [5459, 5777, 10877, 16109, 18971, 22499, 24569, 25199,
494494
40309, 58519, 75077, 97439]:
@@ -519,9 +519,9 @@ def test_isprime_perfect_squares(self):
519519
self.assertIs(isprime(k*k), False)
520520

521521
def test_isprime_word_boundaries(self):
522-
# Exercise the boundaries between the base sets.
522+
# Exercise word boundaries and the boundary between the base sets.
523523
isprime = self.module.isprime
524-
for boundary in [2**32, 4759123141]:
524+
for boundary in [2**32, 4759123141, 2**63]:
525525
for n in range(boundary - 200, boundary + 200):
526526
with self.subTest(n=n):
527527
self.assertIs(isprime(n), py_isprime(n))
@@ -530,6 +530,17 @@ def test_isprime_word_boundaries(self):
530530
self.assertIs(isprime(n), py_isprime(n))
531531
self.assertIs(isprime(2**64 - 59), True) # largest prime < 2**64
532532

533+
def test_isprime_many_squarings(self):
534+
# Exercise repeated squaring when n - 1 has many factors of two,
535+
# including large moduli such as the prime 2**64 - 2**32 + 1.
536+
isprime = self.module.isprime
537+
for s in range(1, 64):
538+
for d in [1, 3, 5, 2**(64 - s) - 1]:
539+
n = (d << s) + 1
540+
if n < 2**64:
541+
with self.subTest(n=n):
542+
self.assertIs(isprime(n), py_isprime(n))
543+
533544
def test_isprime_large_values(self):
534545
isprime = self.module.isprime
535546
self.assertIs(isprime(2**61 - 1), True) # Mersenne prime

‎Modules/mathintegermodule.c‎

Lines changed: 86 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,11 @@ math_integer_isqrt(PyObject *module, PyObject *n)
491491
verified against the Feitsma-Galway exhaustive list of base-2 strong
492492
pseudoprimes below 2**64 (see http://miller-rabin.appspot.com/ and
493493
http://ntheory.org/pseudoprimes.html).
494+
495+
Modular exponentiation and squaring use Montgomery arithmetic with
496+
R = 2**64 to avoid division in their inner loops (P. L. Montgomery,
497+
"Modular multiplication without trial division", Mathematics of
498+
Computation 44 (1985), 519-521).
494499
*/
495500

496501
static const uint8_t small_primes[] = {
@@ -540,44 +545,96 @@ mulmod_u64(uint64_t a, uint64_t b, uint64_t n)
540545
}
541546
#endif
542547

543-
static uint64_t
544-
powmod_u64(uint64_t b, uint64_t e, uint64_t n)
548+
/* High 64 bits of the full product a * b. */
549+
static inline uint64_t
550+
mulhi_u64(uint64_t a, uint64_t b)
545551
{
546-
uint64_t result = 1;
547-
b %= n;
548-
while (e) {
549-
if (e & 1) {
550-
result = mulmod_u64(result, b, n);
551-
}
552-
b = mulmod_u64(b, b, n);
553-
e >>= 1;
554-
}
555-
return result;
552+
#if defined(HAVE_GCC_UINT128_T)
553+
return (uint64_t)(((__uint128_t)a * b) >> 64);
554+
#elif defined(_MSC_VER) && defined(_M_X64)
555+
uint64_t hi;
556+
(void)_umul128(a, b, &hi);
557+
return hi;
558+
#else
559+
/* Split the operands into 32-bit limbs. Each sum fits in uint64_t. */
560+
uint64_t a0 = (uint32_t)a, a1 = a >> 32;
561+
uint64_t b0 = (uint32_t)b, b1 = b >> 32;
562+
uint64_t t = a1 * b0 + ((a0 * b0) >> 32);
563+
uint64_t carry = t >> 32;
564+
t = a0 * b1 + (uint32_t)t;
565+
return a1 * b1 + carry + (t >> 32);
566+
#endif
556567
}
557568

558-
/* Strong probable prime test to the given base. n must be odd and
559-
coprime to the base (a base divisible by n gives no information and
560-
is treated as passing). */
569+
typedef struct {
570+
uint64_t n;
571+
uint64_t inverse; // n**-1 modulo 2**64
572+
uint64_t one; // 2**64 modulo n
573+
uint64_t d; // Odd part of n - 1
574+
int s; // n - 1 = d * 2**s
575+
} montgomery_context;
576+
577+
/* Precompute once for all bases. n must be odd and greater than 1. */
578+
static montgomery_context
579+
montgomery_init(uint64_t n)
580+
{
581+
montgomery_context ctx = {
582+
.n = n,
583+
.inverse = 1,
584+
.one = (0 - n) % n,
585+
.d = n - 1,
586+
.s = 0,
587+
};
588+
/* Newton iteration doubles the number of correct inverse bits,
589+
starting with one correct bit because n is odd. */
590+
for (int i = 0; i < 6; i++) {
591+
ctx.inverse *= 2 - n * ctx.inverse;
592+
}
593+
while ((ctx.d & 1) == 0) {
594+
ctx.d >>= 1;
595+
ctx.s++;
596+
}
597+
return ctx;
598+
}
599+
600+
/* Return a * b * R**-1 modulo n, where R = 2**64 and a, b < n.
601+
With t = a*b and m = (t mod R) * n**-1 mod R, t - m*n is divisible
602+
by R, and (t - m*n) / R lies strictly between -n and n. Subtracting
603+
avoids the possible 129-bit carry of the additive Montgomery form.
604+
All uint64_t arithmetic intentionally wraps modulo R. */
605+
static inline uint64_t
606+
montgomery_mul(uint64_t a, uint64_t b, const montgomery_context *ctx)
607+
{
608+
uint64_t lo = a * b, hi = mulhi_u64(a, b);
609+
uint64_t mhi = mulhi_u64(lo * ctx->inverse, ctx->n);
610+
uint64_t result = hi - mhi;
611+
return hi < mhi ? result + ctx->n : result;
612+
}
613+
614+
/* Strong probable prime test to the given base, in Montgomery form.
615+
A base divisible by n gives no information and is treated as passing. */
561616
static int
562-
sprp_u64(uint64_t n, uint64_t base)
617+
sprp_u64(const montgomery_context *ctx, uint64_t base)
563618
{
564-
base %= n;
619+
base %= ctx->n;
565620
if (base == 0) {
566621
return 1;
567622
}
568-
uint64_t d = n - 1;
569-
int s = 0;
570-
while ((d & 1) == 0) {
571-
d >>= 1;
572-
s++;
623+
// Represent x by x*R modulo n throughout exponentiation.
624+
uint64_t b = mulmod_u64(base, ctx->one, ctx->n);
625+
uint64_t a = ctx->one;
626+
for (uint64_t e = ctx->d; e; e >>= 1) {
627+
if (e & 1) {
628+
a = montgomery_mul(a, b, ctx);
629+
}
630+
b = montgomery_mul(b, b, ctx);
573631
}
574-
uint64_t a = powmod_u64(base, d, n);
575-
if (a == 1 || a == n - 1) {
632+
if (a == ctx->one || a == ctx->n - ctx->one) {
576633
return 1;
577634
}
578-
for (int r = 1; r < s; r++) {
579-
a = mulmod_u64(a, a, n);
580-
if (a == n - 1) {
635+
for (int r = 1; r < ctx->s; r++) {
636+
a = montgomery_mul(a, a, ctx);
637+
if (a == ctx->n - ctx->one) {
581638
return 1;
582639
}
583640
}
@@ -595,14 +652,15 @@ isprime_u64(uint64_t n)
595652
if (n < 67 * 67) {
596653
return n > 1;
597654
}
655+
montgomery_context ctx = montgomery_init(n);
598656
const uint32_t *bases = mr_bases;
599657
size_t num_bases = Py_ARRAY_LENGTH(mr_bases);
600658
if (n < 4759123141) {
601659
bases = mr_bases_small;
602660
num_bases = Py_ARRAY_LENGTH(mr_bases_small);
603661
}
604662
for (size_t i = 0; i < num_bases; i++) {
605-
if (!sprp_u64(n, bases[i])) {
663+
if (!sprp_u64(&ctx, bases[i])) {
606664
return 0;
607665
}
608666
}

0 commit comments

Comments
 (0)