Skip to content

Commit

Permalink
Add Deterministic Miller-Rabin Primality Test (TheAlgorithms#484)
Browse files Browse the repository at this point in the history
* feat: add deterministic miller-rabin test

* feat: refactor and commenting

Tasks:
- Refactored code.
- Added some more witness groups.
- Commented code.

* chore: run `gofmt`

* fix: remove old code

* chore: update tests

* fix: deterministic function test

* chore: run `gofmt`

* chore: update function name in doc comment

Co-authored-by: Taj <[email protected]>

* chore: separate tests for the two versions

* chore: `isTrivial` follows the `, ok` idiom

* chore: add named returns for better docs

* chore: use named returns to make code more concise

Co-authored-by: Taj <[email protected]>
  • Loading branch information
raklaptudirm and tjgurwara99 authored Apr 4, 2022
1 parent 628336b commit f38fdca
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 52 deletions.
140 changes: 110 additions & 30 deletions math/prime/millerrabinprimalitytest.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// millerrabinprimalitytest.go
// description: An implementation of Miller-Rabin primality test
// details:
// A simple implementation of Miller-Rabin Primality Test
// [Miller-Rabin primality test Wiki](https://en.wikipedia.org/wiki/Miller–Rabin_primality_test)
// author(s) [Taj](https://github.com/tjgurwara99)
// see millerrabinprimalitytest_test.go
// This file implements two versions of the Miller-Rabin primality test.
// One of the implementations is deterministic and the other is probabilistic.
// The Miller-Rabin test is one of the simplest and fastest known primality
// tests and is widely used.
//
// Authors:
// [Taj](https://github.com/tjgurwara99)
// [Rak](https://github.com/raklaptudirm)

package prime

Expand All @@ -14,25 +15,40 @@ import (
"github.com/TheAlgorithms/Go/math/modular"
)

// findD accepts a number and returns the
// odd number d such that num = 2^r * d - 1
func findRD(num int64) (int64, int64) {
r := int64(0)
d := num - 1
// formatNum accepts a number and returns the
// odd number d such that num = 2^s * d + 1
func formatNum(num int64) (d int64, s int64) {
d = num - 1
for num%2 == 0 {
d /= 2
r++
s++
}
return d, r
return
}

// MillerTest This is the intermediate step that repeats within the
// miller rabin primality test for better probabilitic chances of
// receiving the correct result.
func MillerTest(d, num int64) (bool, error) {
random := rand.Int63n(num-1) + 2
// isTrivial checks if num's primality is easy to determine.
// If it is, it returns true and num's primality. Otherwise
// it returns false and false.
func isTrivial(num int64) (prime bool, trivial bool) {
if num <= 4 {
// 2 and 3 are primes
prime = num == 2 || num == 3
trivial = true
} else {
prime = false
// number is trivial prime if
// it is divisible by 2
trivial = num%2 == 0
}

res, err := modular.Exponentiation(random, d, num)
return
}

// MillerTest tests whether num is a strong probable prime to a witness.
// Formally: a^d ≡ 1 (mod n) or a^(2^r * d) ≡ -1 (mod n), 0 <= r <= s
func MillerTest(num, witness int64) (bool, error) {
d, _ := formatNum(num)
res, err := modular.Exponentiation(witness, d, num)

if err != nil {
return false, err
Expand All @@ -55,21 +71,41 @@ func MillerTest(d, num int64) (bool, error) {
return false, nil
}

// MillerRabinTest Probabilistic test for primality of an integer based of the algorithm devised by Miller and Rabin.
func MillerRabinTest(num, rounds int64) (bool, error) {
if num <= 4 {
if num == 2 || num == 3 {
return true, nil
// MillerRandomTest This is the intermediate step that repeats within the
// miller rabin primality test for better probabilitic chances of
// receiving the correct result with random witnesses.
func MillerRandomTest(num int64) (bool, error) {
random := rand.Int63n(num-1) + 2
return MillerTest(num, random)
}

// MillerTestMultiple is like MillerTest but runs the test for multiple
// witnesses.
func MillerTestMultiple(num int64, witnesses ...int64) (bool, error) {
for _, witness := range witnesses {
prime, err := MillerTest(num, witness)
if err != nil {
return false, err
}

if !prime {
return false, nil
}
return false, nil
}
if num%2 == 0 {
return false, nil

return true, nil
}

// MillerRabinProbabilistic is a probabilistic test for primality
// of an integer based of the algorithm devised by Miller and Rabin.
func MillerRabinProbabilistic(num, rounds int64) (bool, error) {
if prime, trivial := isTrivial(num); trivial {
// num is a trivial number
return prime, nil
}
d, _ := findRD(num)

for i := int64(0); i < rounds; i++ {
val, err := MillerTest(d, num)
val, err := MillerRandomTest(num)
if err != nil {
return false, err
}
Expand All @@ -79,3 +115,47 @@ func MillerRabinTest(num, rounds int64) (bool, error) {
}
return true, nil
}

// MillerRabinDeterministic is a Deterministic version of the Miller-Rabin
// test, which returns correct results for all valid int64 numbers.
func MillerRabinDeterministic(num int64) (bool, error) {
if prime, trivial := isTrivial(num); trivial {
// num is a trivial number
return prime, nil
}

switch {
case num < 2047:
// witness 2 can determine the primality of any number less than 2047
return MillerTest(num, 2)
case num < 1_373_653:
// witnesses 2 and 3 can determine the primality
// of any number less than 1,373,653
return MillerTestMultiple(num, 2, 3)
case num < 9_080_191:
// witnesses 31 and 73 can determine the primality
// of any number less than 9,080,191
return MillerTestMultiple(num, 31, 73)
case num < 25_326_001:
// witnesses 2, 3, and 5 can determine the
// primality of any number less than 25,326,001
return MillerTestMultiple(num, 2, 3, 5)
case num < 1_122_004_669_633:
// witnesses 2, 13, 23, and 1,662,803 can determine the
// primality of any number less than 1,122,004,669,633
return MillerTestMultiple(num, 2, 13, 23, 1_662_803)
case num < 2_152_302_898_747:
// witnesses 2, 3, 5, 7, and 11 can determine the primality
// of any number less than 2,152,302,898,747
return MillerTestMultiple(num, 2, 3, 5, 7, 11)
case num < 341_550_071_728_321:
// witnesses 2, 3, 5, 7, 11, 13, and 17 can determine the
// primality of any number less than 341,550,071,728,321
return MillerTestMultiple(num, 2, 3, 5, 7, 11, 13, 17)
default:
// witnesses 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, and 37 can determine
// the primality of any number less than 318,665,857,834,031,151,167,461
// which is well above the max int64 9,223,372,036,854,775,807
return MillerTestMultiple(num, 2, 3, 5, 7, 11, 13, 17, 19, 23, 31, 37)
}
}
60 changes: 38 additions & 22 deletions math/prime/millerrabinprimalitytest_test.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
// millerrabinprimality_test.go
// description: Test for Miller-Rabin Primality Test
// author(s) [Taj](https://github.com/tjgurwara99)
// see millerrabinprimalitytest.go

package prime

import "testing"

func TestMillerRabinTest(t *testing.T) {
var tests = []struct {
name string
input int64
expected bool
rounds int64
err error
}{
{"smallest prime", 2, true, 5, nil},
{"random prime", 3, true, 5, nil},
{"neither prime nor composite", 1, false, 5, nil},
{"random non-prime", 10, false, 5, nil},
{"another random prime", 23, true, 5, nil},
}
var tests = []struct {
name string
input int64
expected bool
rounds int64
err error
}{
{"smallest prime", 2, true, 5, nil},
{"random prime", 3, true, 5, nil},
{"neither prime nor composite", 1, false, 5, nil},
{"random non-prime", 10, false, 5, nil},
{"another random prime", 23, true, 5, nil},
}

func TestMillerRabinProbabilistic(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
output, err := MillerRabinTest(test.input, test.rounds)
output, err := MillerRabinProbabilistic(test.input, test.rounds)
if err != test.err {
t.Errorf("For input: %d, unexpected error: %v, expected error: %v", test.input, err, test.err)
}
Expand All @@ -34,8 +30,28 @@ func TestMillerRabinTest(t *testing.T) {
}
}

func BenchmarkMillerRabinPrimalityTest(b *testing.B) {
func TestMillerRabinDeterministic(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
output, err := MillerRabinDeterministic(test.input)
if err != test.err {
t.Errorf("For input: %d, unexpected error: %v, expected error: %v", test.input, err, test.err)
}
if output != test.expected {
t.Errorf("For input: %d, expected %v", test.input, output)
}
})
}
}

func BenchmarkMillerRabinProbabilistic(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = MillerRabinProbabilistic(23, 5)
}
}

func BenchmarkMillerRabinDeterministic(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = MillerRabinTest(23, 5)
_, _ = MillerRabinDeterministic(23)
}
}

0 comments on commit f38fdca

Please sign in to comment.