Basics of Computational Number Theory

Robert Campbell


Contents

  1. Introduction
  2. Modular Arithmetic
  3. Prime Numbers
  4. Other Fields
    Appendices
  1. Programming Notes
  2. References
  3. Glossary

1. Introduction

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.


2. Modular Arithmetic

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.

+ (mod ) =
* (mod ) =

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.

GCD - The Euclidean Algorithm

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 gcd(n, m).

The algorithm to find gcd(n, m) runs something like this:

  1. Write down both n and m
  2. Reduce the larger modulo the smaller
  3. Repeat step 2 until the result is zero
  4. Publish the preceding result as gcd(n, m)

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:

gcd(, ) =

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 & Modular Inverses

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:

  1. Write down n, m, and the two-vectors (1,0) and (0,1)
  2. Divide the larger of the two numbers by the smaller - call this quotient q
  3. Subtract q times the smaller from the larger (ie reduce the larger modulo the smaller)
  4. Subtract q times the vector corresponding to the smaller from the vector corresponding to the larger
  5. Repeat steps 2 through 4 until the result is zero
  6. Publish the preceding result as gcd(n,m)

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 1=-13*53+23*30, so we conclude that 30(-1)=23(mod 53). (This can be confirmed by checking that 23*30=1(mod 53).)

The following short program will allow you to compute examples of the extended Euclidean Algorithm:

gcd(, ) =

Exponentiation - The Russian Peasant 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:

^ (mod ) =

The (slightly cryptic) output of this program can be read as:

  1. The base two digits of the exponent
  2. The successive squarings of the base (ie b2, b4, b8, ...)
  3. The product of the appropriate squares

3. Prime Numbers

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)?

Finding Primes

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 value of using a pseudoprime test to test for primality is that it can be repeated. If the test always responds that the number might be prime then you get some confidence (but no assurance) that N is prime.

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:

  1. Choose a base b for the test.
  2. If b(N-1)=1(mod N) then N might be prime, if it is any other value then N is definitely composite.

Lets try a few examples:

  1. Test 35=5*7 for primality:
    1. Choose base 2
    2. Compute 234 (mod 35) = 9
    3. As the result is not 1 we conclude that 35 is composite.
  2. Test 31 (known to be prime) for primality:
    1. Choose base 2
    2. Compute 230 (mod 31) = 1
      So 31 is a pseudoprime for base 2.
    3. Also compute:
      330 (mod 31) = 1
      530 (mod 31) = 1
    4. From this see that 31 is a pseudoprime for bases 2, 3, and 5, and we conclude that 31 is probably prime.
  3. Test 341=11*31 for primality:
    1. Choose base 2
    2. Compute 2340 (mod 341) = 1
      So 341 is a pseudoprime for base 2.
    3. Choose base 3
    4. Compute 3340 (mod 341) = 56
      So 341 is definitely composite.

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:

^ (mod ) =

Element Orders

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 b(p-1) =1(mod p).

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 b(q-1)(mod q) is arbitrary and rarely equal to 1.

Factoring

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:

  1. Choose a base b.
  2. Start with an exponent of e=2.
    1. Replace b with be(mod N)
    2. If gcd(N,b-1) is not 1, publish it as a factor and STOP.
    3. Add 1 to e.

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 b(p-1)=1(mod p). If we can magically find an exponent E such that (p-1) divides E, say E=k(p-1), then bE =bk =1(mod p). A potential problem is that we don't know the value of p in advance. We get around that by assuming that the largest prime factor of (p-1) is some bound B. If this is true, then by the Bth step of the algorithm we have computed gcd(N,b2*3*...*B-1). We expect that b2*3*...*B=1(mod p) so b2*3*...*B-1=0(mod p), and b2*3*...*B-1(mod N), must be a multiple of q. Thus we expect gcd(N,b2*3*...*B-1) will be q.

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.

Factor using trial base ... to get a factor of

4. Other Groups and Rings

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.

Gaussian 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 (2+i)(2-i) = 4-(-1) = 5 shows that not every integer which is prime in the integers is prime in the Gaussian Integers.

Galois Fields

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 x3+x+1. Neither 0 nor 1 satisfies this polynomial mod 2. Now pretend that there is some solution or root of the polynomial, call it a and add this symbol to our original field. The thing we now have is called F2[a]. The elements of this thing have the form n+ma+ka2. Note that there are no elements that are cubic in a as a3 can be replaced by -a-1 as a was presumed to be a root of the original polynomial. Enumerating the 23=8 elements we have {0, 1, a, a+1, a2, a2+1, a2+a, a2+a+1}.

Appendices

A. Basic Programming Notes

Size & Multiple Precision

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.

Programming Languages

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.

B. References


Robert Campbell
Last modified: Dec 27, 1997