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 a 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 20 digits, the largest modulus allowed is 10 digits.
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. 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 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 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.
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 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 tangible example:
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
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 or 232.
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 64-bit computations
on any machine, thus allowing m to take on values
as large as 232=4294967296.
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.