Miller-Rabin primality test

% Remco Bloemen % 2014-02-27, last updated 2014-03-04

As an exercise I implemented the Miller-Rabin primality test in plain C++. It turns out this algorithms lends itself for a true festival of operators, so I couldn’t resist making the code very dense:

bool MillerRabin(uint64 n, uint64 k)
{
	if(n == k) return true;
	uint64 s, d, b, e, x;

	// Factor n-1 as d 2^s
	for(s = 0, d = n - 1; !(d & 1); s++)
		d >>= 1;

	// x = k^d mod n using exponentiation by squaring
	// The squaring overflows for n >= 2^32
	for(x = 1, b = k % n, e = d; e; e >>= 1) {
		if(e & 1)
			x = (x * b) % n;
		b = (b * b) % n;
	}

	// Verify k^(d 2^[0…s-1]) mod n != 1
	if(x == 1 || x == n-1)
		return true;
	while(s-- > 1) {
		x = (x * x) % n;
		if(x == 1)
			return false;
		if(x == n-1)
			return true;
	}
	return false;
}

That’s it! Now, the algorithm will overflow for n \geq 2^{32} but I intend to use it to find 64-bit primes, so I’ll have to work on that.

Interestingly, the "primes page" claims that (source):

If n < 4,759,123,141 is a 2, 7 and 61-SPRP, then n is prime.

(A number n is called “k-SPRP” or “strong probable-prime base k” if it passes Miller Rabin with this k)

Since 4 759 123 141 is greater that 2^{32} we can use this to implement a fast and exact function to determine whether some 32 bit number is prime. I special cased the first several primes to improve performance and made the whole thing into an incomprehensible operator soup, just like the Miller-Rabin implementation:

bool isPrime(uint32 n)
{
	return (n>73&&!(n%2&&n%3&&n%5&&n%7&&
	n%11&&n%13&&n%17&&n%19&&n%23&&n%29&&
	n%31&&n%37&&n%41&&n%43&&n%47&&n%53&&
	n%59&&n%61&&n%67&&n%71&&n%73))?false:
	MillerRabin(n, 2)&&MillerRabin(n, 7)
	&&MillerRabin(n, 61);
}

64-bits

In the previous post I showed an implementation of Miller-Rabin that overflowed when the number was more that 32 bits. In this post I present a way to fix this.

I could remember an instruction I used before, mulq which does 128 bit multiplication. It multiplies two 64 bit number and stores the least significant 64 bits in rax and the significant half in rdx.

Now we still need to reduce this number modulo m. Originally I planned to create a table of 2^n \mod m, use the bits of rdx to add them and then add rax % m. But after consulting this x86-64 manual I found out about divq, which takes a 128 bit number stored in rdx:rax, a 64 bit number m and produces the quotient and remainder. The whole thing can be coded as:

/// Modular multiplication
/// @param a The first factor, a < m
/// @param a The second factor, b < m
/// @param m The modulus
/// @return The reduced product, a b mod m < m
uint64 mul(uint64 a, uint64 b, uint64 m)
{
		// Perform 128 multiplication and division
		uint64 q; // q = ⌊a b / m⌋
		uint64 r; // r = a b mod m
		asm("mulq %3;"
		    "divq %4;"
		    : "=a"(q), "=d"(r)
		    : "a"(a), "rm"(b), "rm"(m));
		return r;
}

This should work fast, but I still need to look into Montgomery reduction. Perhaps I should benchmark the two.

Modular exponentiation can now be implemented using exponentiation by squaring:

/// Modular exponentiation
/// @param b The base, b < m
/// @param e The exponent
/// @param m The modulus
/// @returns The reduced power of a, a^b mod m
uint64 pow(uint64 b, uint64 e, uint64 m)
{
	uint64 r = 1;
	for(; e; e >>= 1) {
		if(e & 1) r = mul(r, b, m);
		b = mul(b, b, m);
	}
	return r;
}

And the Miller-Rabin test becomes:

/// Miller-Rabin probabilistic primality testing
/// @param n The number to test for  primality
/// @param k The witness for primality
/// @returns True iff when n is a k-stong pseudoprime
bool MillerRabin(uint64 n, uint64 k)
{
	// Factor n-1 as d*2^s
	uint64 s = 0;
	uint64 d = n - 1;
	for(; !(d & 1); s++)
		d >>= 1;

	// Verify x = k^(d 2^i) mod n != 1
	uint64 x = pow(k % n, d, n);
	if(x == 1 || x == n-1) return true;
	while(s-- > 1) {
		// x = x^2 mod n
		x = mul(x, x, n);
		if(x == 1) return false;
		if(x == n-1) return true;
	}
	return false;
}

Together with the prime sieve I created earlier a fast probablilistic prime checking procedure can be developed:

const vector<uint64> small_primes = prime_sieve(1<<20);

/// Miller-Rabin probabilistic primality testing
/// @param n The number to test for  primality
/// @returns False when n is not a prime
bool is_prime(uint64 n)
{
	// Handle small primes fast
	for(int i = 0; i < small_primes.size(); i++) {
		uint64 p = small_primes[i];
		if(n == p) return true;
		if(n % p == 0) return false;
	}

	// Do a few Miller-Rabin rounds
	for(int i = 0; i < 10; i++) {
		if (!MillerRabin(n, small_primes[i])) return false;
	}
	
	return true;
}

This algorithm correctly identifies the first ten primes less than 2^{64}. But then again, so does a single Miller-Rabin round with K= 17. Perhaps I should implement the Baillie-PSW algorithm, there are rumours that it has no false positives for 64 bits!

Remco Bloemen
Math & Engineering
https://2π.com