Skip to content

Commit fbb5089

Browse files
committed
Add the Miller-Rabin primality test
1 parent eef060e commit fbb5089

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
describe("Miller-Rabin probabilistic primality test", function()
2+
local test_prime = require("math.prime.miller_rabin_test")
3+
it("works for small primes & composite numbers", function()
4+
local is_prime = require("math.prime.sieve_of_eratosthenes")(1e6)
5+
for number = 1, 100 do -- covers all edge cases
6+
assert.equal(is_prime[number], test_prime(number, 20))
7+
end
8+
for _ = 1, 1e5 do
9+
local number = math.random(101, 1e6)
10+
assert.equal(is_prime[number], test_prime(number, 20))
11+
end
12+
end)
13+
it("works for selected large primes", function()
14+
for _, prime in pairs({ 6199, 7867, 2946901, 39916801 }) do
15+
assert.truthy(test_prime(prime))
16+
end
17+
end)
18+
it("works for random composite numbers", function()
19+
for _ = 1, 1e3 do
20+
-- Care is taken to stay well within double (and int) bounds
21+
local composite = math.random(2 ^ 7, 2 ^ 13) * math.random(2 ^ 7, 2 ^ 13)
22+
assert.falsy(test_prime(composite))
23+
end
24+
end)
25+
end)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
-- Miller-Rabin primality test; nondeterministic variant
2+
-- Be careful when using this with doubles as precision issues may lead to incorrect results
3+
4+
-- Simple integer exponentiation by squaring mod some number
5+
-- Apply mod after (almost) every operation to not run into issues with double precision
6+
local function modpow(base, exp, mod)
7+
if exp == 1 then
8+
return base
9+
end
10+
if exp % 2 == 1 then
11+
return (modpow(base, exp - 1, mod) * base) % mod
12+
end
13+
return modpow(base, exp / 2, mod) ^ 2 % mod
14+
end
15+
16+
return function(
17+
n, -- number to test for primality; may not exceed (2^52)^.5 = 2^26 = 67108864 due to double limitations
18+
rounds -- rounds determine accuracy: probability that a composite is considered probably prime does not exceed 4^-k
19+
)
20+
-- Handle edge cases
21+
if n == 1 then
22+
return false
23+
end
24+
if n % 2 == 0 then
25+
return n == 2
26+
end
27+
if n == 3 then
28+
return true
29+
end
30+
31+
rounds = rounds or 100 -- decent default for a false positive probability < 1e-60
32+
33+
-- Write n as d*2^r + 1
34+
local d = n - 1
35+
local r = 0
36+
while d % 2 == 0 do
37+
r = r + 1
38+
d = d / 2
39+
end
40+
41+
for _ = 1, rounds do
42+
local a = math.random(2, n - 2)
43+
local x = modpow(a, d, n) -- a^d % n
44+
if x ~= 1 and x ~= n - 1 then
45+
local composite = true
46+
for _ = 1, rounds - 1 do
47+
x = x ^ 2 % n
48+
if x == n - 1 then
49+
composite = false
50+
break
51+
end
52+
end
53+
if composite then
54+
return false -- certainly not prime
55+
end
56+
end
57+
end
58+
59+
return true -- likely prime (confidence based on rounds)
60+
end

0 commit comments

Comments
 (0)