Skip to content

Commit d77fa0d

Browse files
committed
Add Miller-Rabin primality test C# and Python implementation
1 parent 8f0e599 commit d77fa0d

File tree

4 files changed

+194
-1
lines changed

4 files changed

+194
-1
lines changed

Readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Basically these are supposed to be my notes, but feel free to use them as you wi
6161
### [Primality Test](primalityTest)
6262
- [Optimised School Method](primalityTest/optimisedSchoolMethod) (Check factors till √n)
6363
- [Fermat Method](primalityTest/fermatMethod)
64-
- Miller-Rabin Method
64+
- [Miller-Rabin Method](primalityTest/millerRabinMethod)
6565
- [Solovay-Strassen Method](primalityTest/solovayStrassenMethod)
6666

6767
### [Classic Alogorithms](ClassicalAlgos)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System.Numerics;
2+
using System.Security.Cryptography;
3+
4+
namespace Algorithms.primalityTest
5+
{
6+
7+
/// <summary>
8+
/// Collection of support tools for primality test algorithms
9+
/// Random BigInteger implementation from https://stackoverflow.com/a/48855115/12771343
10+
/// </summary>
11+
public static partial class Algorithms
12+
{
13+
14+
/// <summary>
15+
/// Generate a random BigInteger between <paramref name="min"/> and <paramref name="max"/>, inclusive.
16+
/// </summary>
17+
/// <param name="min">BigInter lower bound.</param>
18+
/// <param name="max">BigInteger upper bound.</param>
19+
/// <returns>
20+
/// A random BigInteger in [<paramref name="min"/>, <paramref name="max"/>].
21+
/// </returns>
22+
public static BigInteger RandomInRange(BigInteger min, BigInteger max)
23+
{
24+
25+
// If min > max we swap the values
26+
if (min > max)
27+
{
28+
max += min;
29+
min = max - min;
30+
max -= min;
31+
}
32+
33+
// Offset to set min = 0
34+
max -= min;
35+
36+
// Retrieve the random number and offset to get the desired interval
37+
BigInteger value = RandomInRangeFromZeroToPositive(max) + min;
38+
return value;
39+
}
40+
41+
/// <summary>
42+
/// Generate a random BigInteger smaller or equal to <paramref name="max"/>.
43+
/// </summary>
44+
/// <param name="max">BigInteger upper bound.</param>
45+
/// <returns>
46+
/// A random BigInteger in [0, <paramref name="max"/>].
47+
/// </returns>
48+
private static BigInteger RandomInRangeFromZeroToPositive(BigInteger max)
49+
{
50+
using RandomNumberGenerator rng = RandomNumberGenerator.Create();
51+
BigInteger value;
52+
byte[] bytes = max.ToByteArray();
53+
54+
// NOTE: sign bit is always 0 because `max` must always be positive
55+
byte zeroBitsMask = 0b00000000;
56+
57+
// Count how many bits of the most significant byte are 0
58+
var mostSignificantByte = bytes[bytes.Length - 1];
59+
60+
// Try to set to 0 as many bits as there are in the most significant byte, starting from the left (most significant bits first)
61+
// NOTE: `i` starts from 7 because the sign bit is always 0
62+
for (var i = 7; i >= 0; i--)
63+
{
64+
// Keep iterating until the most significant non-zero bit
65+
if ((mostSignificantByte & (0b1 << i)) != 0)
66+
{
67+
var zeroBits = 7 - i;
68+
zeroBitsMask = (byte)(0b11111111 >> zeroBits);
69+
break;
70+
}
71+
}
72+
73+
do
74+
{
75+
rng.GetBytes(bytes);
76+
77+
// Set most significant bits to 0 (because if any of these bits is 1 `value > max`)
78+
bytes[bytes.Length - 1] &= zeroBitsMask;
79+
80+
value = new BigInteger(bytes);
81+
82+
// `value > max` 50% of the times, in which case the fastest way to keep the distribution uniform is to try again
83+
} while (value > max);
84+
85+
return value;
86+
}
87+
88+
89+
}
90+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System.Numerics;
2+
3+
namespace Algorithms.primalityTest
4+
{
5+
6+
public static partial class Algorithms
7+
{
8+
9+
/// <summary>
10+
/// Miller-Rabin primality test
11+
/// </summary>
12+
/// <remarks>
13+
/// Test if <paramref name="number"/> could be prime using the Miller-Rabin Primality Test with <paramref name="rounds"/> rounds.
14+
/// A return value of false means <paramref name="number"/> is definitely composite, while true means it is probably prime.
15+
/// The higher <paramref name="rounds"/> is, the more accurate the test is.
16+
/// </remarks>
17+
/// <param name="number">The number to be tested for primality.</param>
18+
/// <param name="rounds">How many rounds to use in the test.</param>
19+
/// <returns>A bool indicating if the number could be prime or not.</returns>
20+
public static bool MillerRabin(BigInteger number, int rounds)
21+
{
22+
// Handle corner cases
23+
if (number == 1)
24+
return false;
25+
if (number == 2)
26+
return true;
27+
28+
// Factor out the powers of 2 from {number - 1} and save the result
29+
BigInteger d = number - 1;
30+
int r = 0;
31+
while (d % 2 == 0)
32+
{
33+
d /= 2;
34+
++r;
35+
}
36+
37+
BigInteger x, a;
38+
// Cycle at most {round} times
39+
for (; rounds > 0; --rounds)
40+
{
41+
a = RandomInRange(2, (number - 1));
42+
x = BigInteger.ModPow(a, d, number);
43+
if (x == 1 || x == number - 1)
44+
continue;
45+
// Cycle at most {r - 1} times
46+
for (int tmp = 0; tmp < r - 1; ++tmp)
47+
{
48+
x = BigInteger.ModPow(x, 2, number);
49+
if (x == number - 1)
50+
break;
51+
}
52+
if (x == number - 1)
53+
continue;
54+
return false;
55+
}
56+
return true;
57+
}
58+
}
59+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import random
2+
3+
4+
def miller_rabin(number: int, rounds: int) -> bool:
5+
"""Miller-Rabin primality test
6+
7+
Test if number could be prime using the Miller-Rabin Primality Test with rounds rounds.
8+
A return value of false means number is definitely composite, while true means it is probably prime.
9+
The higher rounds is, the more accurate the test is.
10+
:param int number: The number to be tested for primality.
11+
:param int rounds: How many rounds to use in the test.
12+
:return: A bool indicating if the number could be prime or not.
13+
:rtype: bool
14+
15+
"""
16+
# Handle corner cases
17+
if number == 1:
18+
return False
19+
if number == 2:
20+
return True
21+
22+
# Factor out the powers of 2 from {number - 1} and save the result
23+
d = number - 1
24+
r = 0
25+
while d % 2 == 0:
26+
d /= 2
27+
r += 1
28+
d = int(d)
29+
30+
# Cycle at most {round} times
31+
for i in range(rounds + 1):
32+
a = random.randint(2, number - 2)
33+
x = pow(a, d, number)
34+
if x == 1 or x == number - 1:
35+
continue
36+
# Cycle at most {r - 1} times
37+
for e in range(r):
38+
x = x * x % number
39+
if x == number - 1:
40+
break
41+
if x == number - 1:
42+
continue
43+
return False
44+
return True

0 commit comments

Comments
 (0)