This document is a gentle introduction to computational number theory. The plan of the paper is to first give a quick overview of arithmetic in the modular integers. Throughout, we will emphasize computation and practical results rather than delving into the why. Simple programs, generally in JavaScript, are available for all of the algorithms mentioned. At the end of the paper we will introduce the Gaussian Integers and Galois Fields and compare them to the modular integers. Companion papers will examine number theory from a more advanced perspective.
Modular arithmetic is arithmetic using integers modulo some fixed integer N. Thus, some examples of operations modulo 12 are:
Further examples can be generated and checked out with the following short programs. Note that, as JavaScript cannot compute with integers larger than 53 bits, the largest modulus allowed is 26.5 bits, or about 94,900,000.
Among the basic operations we have missed the division operator. If we were working in the integers we would almost never be able to define a quotient (unless the answer is itself an integer). In the modular integers we can often, but not always, define a quotient:
As the last example points out, modular division does not always produce a unique result, for other correct answers are 3 and 11 (as 3 * 3 = 9 (mod 12) and 3 * 11 = 33 = 9 (mod 12)). In particular, if the modulus and the divisor share a common factor (in this case 3 divides both 3 and 12), the answer will not be unique if it exists at all. We draw two conclusions from this - the first is that we would like to be able to spot such common factors, and the second is that we would like to avoid such situations. Usually we avoid the situation ever showing up by using only prime moduli. Spotting the situation is one of the results of the next section.
The Euclidean Algorithm solves two problems we have posed:
The largest number which divides two numbers
n and m is called the greatest common divisor
of n and m, and denoted
The algorithm to find gcd(n, m) runs something like this:
An example of this algorithm is the following computation of gcd(120,222):
120 222
120 222-120=102
120-102=18 102
18 102-5*18=12
18-12=6 12
6 12-2*6=0
Thus gcd(120,222)=6.
The following short program will allow you to compute examples of the Euclidean Algorithm:
If you think carefully about the Euclidean algorithm you will see that at each step both numbers are formed by adding some multiples of the original numbers. Thus there are some numbers, a and b, such that gcd(n,m)=a*n+b*m. The Extended Euclidean algorithm can recover not only gcd(n,m), but also these numbers a and b.
The Extended Euclidean algorithm not only computes gcd(n,m), but also returns the numbers a and b such that gcd(n,m)=a*n+b*m. If gcd(n,m)=1 this solves the problem of computing modular inverses.
Assume that we want to compute n(-1)(mod m) and further assume that gcd(n,m)=1. Run the Extended Euclidean algorithm to get a and b such that a*n+b*m=1. Rearranging this result, we see that a*n=1-b*m, or a*n=1(mod m). This solves the problem of finding the modular inverse of n, as this shows that n(-1)=a(mod m).
The Extended Euclidean algorithm is nothing more than the usual Euclidean algorithm, with side computations to keep careful track of what combination of the original numbers n and m have been added at each step. The algorithm runs generally like this:
An example of this algorithm is the following computation of 30(-1)(mod 53):
53 30 (1,0) (0,1)
53-1*30=23 30 (1,0)-1*(0,1)=(1,-1) (0,1)
23 30-1*23=7 (1,-1) (0,1)-1*(1,-1)=(-1,2)
23-3*7=2 7 (1,-1)-3*(-1,2)=(4,-7) (-1,2)
2 7-3*2=1 (4,-7) (-1,2)-3*(4,7)=(-13,23)
2-2*1=0 1 (4,-7)-2*(-13,23)=(30,-53) (-13,23)
From this we see that gcd(30,53)=1 and, rearranging terms, we see
that
The following short program will allow you to compute examples of the extended Euclidean Algorithm:
When computing a power of a number with a finite modulus there are efficient ways to do it and inefficient ways to do it. In this section we will outline a commonly used efficient method which has the curious name "the Russian Peasant algorithm".
The most obvious way to compute 1210 (mod 23) is to multiply 12 a total of nine times, reducing the result mod 23 at each step. A more efficient method which takes only four multiplications is accomplished by first noting that:
122=6 (mod 23)
124=62=13 (mod 23)
128=132=8 (mod 23)
We have now performed three squarings and, by noting that the exponent breaks into powers of 2 as 10=8+2, we can rewrite our computation:
1210=12(8+2)
=128*122
=8*6=2(mod 23)
So our algorithm consists of writing the exponent as powers of two. (This can be done by writing it as a number base 2 and reading off successive digits - eg 1010=10102.) Now we multiply successive squares of the base number for each digit of the exponent which is a "1".
The following short program will allow you to compute examples of the Russian Peasant method for exponentiation:
The (slightly cryptic) output of this program can be read as:
A prime is a number which has no divisors other than 1 and the number itself.
Thus, 13 is a prime but 12 is not - it has divisors 2, 3, and 6. A number which is not prime is said to be composite.
The idea of primes raises several questions - how does one find a prime and how do you reduce a number into a product of its prime factors (ie how do you factor it)?
There are two ways to find primes. The oldest is to search a large number of numbers, sieving out the non-prime (ie composite) numbers, leaving only the primes. The second method is to test an individual number for primality.
The Sieve of Eratosthenes is a recent method
(dating from the 3rd century BC). The idea is this - write
down all the integers up to some point:
2 3 4 5 6 7 8 9 10 11 12 13 14 15.
Now cross out all the even ones larger than 2:
2 3 4 5 6 7 8
9 10 11 12 13
14 15.
Now cross out all those divisible by 3 (other than 3):
2 3 4 5 6 7 8
9 10 11 12 13
14 15.
At each step we find the next prime (number not crossed out)
publish it as a prime and cross out all of its multiples.
So, in this example we see that the numbers 2, 3, 5, 7, 11,
and 13 are prime.
While the sieve is an efficient way to find a large number of successive primes it cannot be used to test an arbitrary number for primality. This can be done with a pseudoprime test.
A pseudoprime test would be more accurately called a compositeness test as when it is applied to an integer N it returns one of two possible results:
The following is the simplest pseudoprime test. In the literature it is normally not given a name but as it is based on Fermat's Little Theorem (stated in the next section) I will call it the Fermat test. This test runs like this:
Lets try a few examples:
The last example should be somewhat disturbing as it shows that a composite number can masquerade as a prime under the Fermat pseudoprime test. For this there is both good news and bad news.
More examples of this test can be computed by using the previously mentioned method of fast exponentiation:
Why did the primality test work? The Fermat pseudoprime test, as well as a number of others, depends on a simple property of primes first noticed by Pierre Fermat in the 17th century:
Theorem: (Fermat's Little Theorem) If p is prime
and 0<b<p then
In order to winnow primes from non-primes we make use of the
fact that for a prime p this is true for any base
b, while for a non-prime q and a chosen
base b the value of
The smallest non-zero exponent e such that ae = 1 (mod N) is the order of a mod N. This is generally denoted o(a) (mod N).
A consequence of Fermat's Little Theorem is that, for prime p and 0<a<p, the order of a mod p divides (p-1).
The simplest way to factor an integer is to trial divide it by all of the primes less than its square root. For large numbers this is a very inefficient method, though. A number of better methods have been developed during the mid and late 20th Century, though. We will describe Pollard's p-1 algorithm.
To use Pollard's p-1 factoring method to factor a composite number N:
The principle behind this method is again Fermat's Little Theorem.
Assume for simplicity that the composite number N has two
prime factors, p and q, so N=pq.
Assume that b does not divide p (an unlikely
circumstance). Now we know that
This method usually works if you are willing to run enough to large enough values of the exponent. Sometimes you are lucky, (p-1) has only small factors, and factorization is easy. Sometimes you are unlucky, (p-1) has a large prime factor and it takes a lot of work to factor N.
A number g is primitive mod p if the order of g mod p is (p-1).
If p is prime, Fermat's Little Theorem that, for any g not divisible by p, g(p-1) = 1 (mod p). If r is not prime, say r = pq, then there are no primitive elements mod r. Conversely, it is (fairly) simple to prove that there are primitive elements mod any prime p.
When p is prime there are many elements which are primitive mod p, but there is no way to tell which elements are primitive short of finding their order. Thus 2 is not primitive mod 7 (21=2, 22=4 and 23=8=1 (mod 7), so the order of 2 mod 7 is 3), but 3 is primitive mod 7 (31=3, 32=9=2, 33=27=6, 34=81=4, 35=243=5 so the order of 3 mod 7 is 6).
(Strictly speaking, we have not given the correct definition of primitivity. A more correct definition is that g is primitive mod N if the successive powers of g mod N produce all integers less than N and coprime to N. This sharper definition allows us to state that there are primitive elements mod N if and only if N has the form p, pn, or 2pn, where p is prime.)
Now think about a number which is not prime, say N=pq, where both
p and q are prime. Consider some a which is coprime to
both p and q (in other words 0<a(mod p)<p,
and similarly for q). Thus from Fermat's Little Theorem we know that
Among other things, we have shown the following, which is a sub-case of what is called Euler's Theorem. (One of the many theorems called "Euler's Theorem".)
Theorem: If N=pq, where both p
and q are prime, and if b is chosen so that gcd(b,N)=1,
then
The Miller-Rabin primality test is, like the Fermat test, a pseudoprime test. It uses a consequence of the structure of the group of units - that the only square roots of 1 mod a prime p are 1 and -1, but mod any odd composite N with n distinct prime factors, there are 2n square roots of 1. Thus, the Miller-Rabin test looks for square roots of 1, declaring N composite if it finds one other than 1 or -1.
Thus far, every test has been a pseudoprime test - it positively identifies composite numbers but can identify primes at best with some likelihood of error. We can use what we now know to produce a test which proves that some number is prime. We know that:
Thus, if we can find an element a such that it has order exactly
(N-1) mod N, then we have proven that N is prime.
Our approach is brute force - we try successive values of a and see if
each is primitive. The work is not as bad as it sounds, though. For any a
we first confirm that a(N-1)=1 (mod N).
Now we know that the true order of a is a divisor of N-1.
Checking an for every divisor n of
N-1 is not bad. There is a second trick we can apply though. If the
order of a is strictly less than N-1 then there must be some
prime divisor p of N-1 such that
Sometimes easier said than done, particularly the part where you factor (N-1).
Number theory is the study of more than just the integers. This is true both because various other algebraic structures are natural generalizations of the integers and also because they often give us information about the integers.
The first generalization of the integers we will touch on is the Gaussian Integers, an extension of the integers by i, the square root of negative one.
The elements of the Gaussian Integers are all numbers of the form n+mi, where both n and m are integers. Addition and multiplication are done in the usual way.
Things start getting interesting when we try to decide
what we mean by primes in the Gaussian Integers. The simple
observation that
Galois fields (also referred to as finite fields) are extensions of the integers modulo some prime p.
We start with the integers modulo p and some polynomial which cannot be solved (ie factored) in this field. Lets continue with a two tangible examples:
Our example will start with the integers
modulo 2 (containing only the two numbers 0 and 1). In keeping
with the usual notation we will call this F2.
Given this field we now pull out of our hat the polynomial
Starting with the naive assumption that the defining polynomial
A bit more interesting is computing powers of elements. Just as in the case of computing with the integers, we can efficiently compute using the Russian Peasant algorithm of squaring and multiplying.
An example of this efficient multiplication is computing a26:
There are a number of different ways in which we can use the basic Russian Peasant algorithm. The following slightly different approach is a little more difficult to understand but cleaner to program as it doesn't require temporary variables:
Finally, we are in a position to see if our polynomial is irreducible mod 5 (without actually factoring it - a problem for another day). The structure of finite fields is similar to that of Zp in a number of ways. An analog of Fermat's Little Theorem exists, so for any element b∈GF(54), we must have b(54-1)=1. Another similarity is that any Galois field is guaranteed to have primitive elements. In this case there are 54=625 elements, of which (54-1)=624 are non-zero. Thus, a primitive element will have order exactly 624. As in the integer case, we check this by noting that 624 has prime factorization 624 = (24)(3)(13).
So (a+1) has order exactly 624 and is primitive. Thus we have shown that the polynomial x4+2x3+3x2+x+1 is irreducible.
Some of the more interesting questions in computational number theory involve large numbers. This can be a problem as most languages and machines only support integers up to a certain fixed size, commonly 264 bits (about 1.6×1019) or 232 bits (about 4×109).
This limit is further reduced by the fact that most of the
algorithms require the computation of intermediate results
twice the size of the number of interest. For example, the
computation mod(a*b,m)
is commonly found, where each of a and b
are roughly the same size as m. For this computation
to work, the intermediate value a*b must
be less than the integer limit of the machine. Thus, on a 32-bit
machine, the largest value of m for which this can
be done is 216=65536. The examples in this paper
are written in JavaScript, which allows 53-bit computations
on any machine, thus allowing m to take on values
as large as 226.5=94,906,265.
The following are simple interpreted languages in which I have written basic number theory programs, suitable for classroom use and modification.
More details on programming for number theory can be found in a related document here.