Bignums

Natural numbers as an abstract data type

If you were designing a programming language, what operations on natural numbers would you build in? More specifically, if you were designing a natural-number data type as a library module, what would the interface look like?

You'd certainly want addition and subtraction, multiplication and division. In the case of division, you'd want to be able to determine both the (integer) quotient and the remainder. You'd probably also want exponentiation -- since the bases and exponents are natural numbers, you wouldn't have to look out for any problematical cases (except for zero to the zeroth power). Various special cases of these operations occur commonly enough to justify functions of their own: adding 1, subtracting 1, doubling, halving (discarding the remainder), squaring, and cubing.

The preceding operations are ``non-destructive'' -- the operands are intact after the operation. You might want destructive versions of a few of them: incrementing or decrementing a natural-number variable by a specified amount and scaling a natural number variable up or down by a specified factor (that is, multiplying it or dividing it by that factor). Again, there are special cases that merit separate procedures: incrementing and decrementing by one, doubling and halving.

You'd want to be able to compare natural numbers; probably, you'd want the full set of six comparison functions (<, =, >, >=, <>, <=), and perhaps also the special case of testing equality with 0. You'd want the max and min functions that select and return the larger or the smaller of two natural-number arguments. You might want a function that takes two natural numbers and determines whether one is a multiple of the other; the special case of determining whether a number is a multiple of 2 would also be handy.

You'd want to be able to read natural numbers from text files (and specifically from standard input) and to write them to text files (and specifically to standard output and standard error); also, independently, to convert natural numbers to and from strings of characters that express those numbers in decimal numeration.

Finally, there's one less obvious operation: You'd want to be able to assign any natural number to any natural-number variable. The assignment operation available in your implementation language might not be appropriate. (For instance, the implementer might choose to represent natural numbers as structures accessed through pointers, in which case the built-in assignment operation would copy the pointer rather than the structure to which it points.)

Language designers have occasionally provided more than this (the Scheme programming language provides functions for computing greatest common divisors and least common multiples, for instance), but this list of operations constitutes the basic repertoire. Here's how the procedure and function headers should look in HP Pascal:

function sum (augend, addend: natural): natural;
function difference (minuend, subtrahend: natural): natural;
function product (multiplicand, multiplier: natural): natural;
function quotient (dividend, divisor: natural): natural;
function remainder (dividend, divisor: natural): natural;
function power (base, exponent: natural): natural;

function successor (given: natural): natural;
function predecessor (given: natural): natural;
function twice (given: natural): natural;
function half (given: natural): natural;
function square (given: natural): natural;
function cube (given: natural): natural;

procedure increment (var given: natural; amount: natural);
procedure decrement (var given: natural; amount: natural);
procedure scale_up (var given: natural; scale: natural);
procedure scale_down (var given: natural; scale: natural);
procedure up1 (var given: natural);
procedure down1 (var given: natural);
procedure double (var given: natural);
procedure halve (var given: natural);

function less (first, second: natural): Boolean;
function equal (first, second: natural): Boolean;
function greater (first, second: natural): Boolean;
function not_less (first, second: natural): Boolean;
function not_equal (first, second: natural): Boolean;
function not_greater (first, second: natural): Boolean;
function is_zero (given: natural): Boolean;

function max (first, second: natural): natural;
function min (first, second: natural): natural;

function is_multiple (first, second: natural): Boolean;
function is_even (given: natural): Boolean;
function is_odd (given: natural): Boolean;

procedure read_natural (var source: text; var legendum: natural);
procedure input_natural (var legendum: natural);
procedure write_natural (var target: text; scribendum: natural);
procedure output_natural (scribendum: natural);
procedure output_natural_to_standard_error (scribendum: natural);

function string_to_natural (var s: string): natural;
procedure natural_to_string (n: natural; var s: string);

procedure assign (var target: natural; source: natural);

Designing and choosing the implementation

Now for the hard part: You'd like all of these operations to work for natural numbers of any size, without the upper bound imposed by MAXINT. It is impossible to meet this goal literally, since there is no upper limit to the size of natural numbers, whereas there is a finite bound on the resources available on any particular computer at any particular time. However, the constraint imposed by the fact that computers are finite is much less oppressive than the constraint imposed by having a fixed number of bits for all natural numbers. We can all live with the fact that our machines can't do everything; what is frustrating is having a machine that could do more but doesn't.

To come as close as possible to the ideal of being able to work with natural numbers of any size, we need a representation that uses a data structure with dynamically allocated components, so that we can take as many as we need to represent whatever natural numbers turn up in our computations. When we consider how to break an arbitrary natural number into components of fixed size, it's plausible to think of a sequence of digits. A strictly linear structure is okay, and since we'll hardly ever want to inspect the middle digits of a number without first looking at either the most or the least significant digits, sequential access to the components is a limitation that we can live with. However, it should be possible to run through the digits from least significant to most significant (for such operations as addition, which is easiest if one works from right to left) or from most significant to least significant (for such algorithms as halving, which is easiest if one works left to right). So a doubly-linked list of digits seems to be the best choice.

The digits need not be decimal digits. In fact, using base ten in the internal representation would mean that a large natural number would have a lot of components, each containing less than four bits of information. Considering that each component will also require two pointers (which on our system occupy thirty-two bits each), this seems a little extravagant. Using a much larger base of numeration would allow us to pack more information into each component.

On the other hand, if we make the base of numeration too large, we run up against the problem of being unable to perform arithmetic on the individual digits without causing overflows. Specifically, the algorithm for long division that will be presented below presupposes that we can directly compute numbers having as many as three digits in whatever base we settle on -- in other words, that the base should not exceed the cube root of MAXINT + 1. Furthermore, it will simplify the parity functions (even and odd), as well as the division function, if we ensure that the base of numeration is an even number.

There are actually two good choices for the base of numeration. To minimize the number of components, we could use the largest even integer not exceeding the square root of MAXINT, which on the HPs turns out to be 1290. To simplify the input and output routines, we could use 1000. In the naturals module that I've developed below, I chose the larger base, but since I've used a defined constant for the base it would be straightforward to switch.

In addition to the components making up the doubly-linked list, it is convenient to have a ``header node'' containing pointers to the most and least significant digits and a count of the number of digits in the number. The individual digits are accessible only through these header nodes; one of the module invariants is that no pointer to an individual component ever escapes from the module. The header nodes are allocated dynamically. The natural data type is simply equated with a pointer to a header node.

Here, then, are the type definitions for the implementation I propose:

const
  BASE = 1290;
     { = largest even integer less than the cube root of MAXINT + 1 }
type
  base_digits = 0 .. BASE - 1;
  link = ^component;
  component = record
                digit: base_digits;
                higher, lower: link
              end;
  header = record
             size: integer;
             highest, lowest: link
           end;
  natural = ^header;
Since our library module will be implemented in a language that already has a data type that includes some natural numbers, we should add transfer functions to the interface so that we can convert between our data type and the one that is built in:

function integer_to_natural (n: integer): natural;
function natural_to_integer (n: natural): integer;
Of course, there are preconditions for both of these functions; integer_to_natural requires a non-negative argument, and natural_to_integer requires an argument less than or equal to MAXINT.

To keep the doubly-linked lists as short as possible and to avoid some useless computation, we can establish as an invariant of the module that our representations of natural numbers will never contain leading zeroes. Any of the procedures or functions that generate new values of type natural will trim off any leading zeroes before returning. Moreover, we shall represent the natural number 0 with a header node in which both of the links are NIL (and the doubly-linked list therefore has no components).

Implementing the functions and procedures

Some of the functions and procedures present interesting implementation problems. The algorithms for working with large natural numbers digit by digit are often similar to those taught in elementary school for hand computation using decimal numeration. (It does not follow that they are easy to program.)

Addition

The basic idea of the addition algorithm is to start at the low end of both operands and to add corresponding digits together, also adding in any carry that is generated from the next lower digit position. If the result is less than the base, put it in the corresponding position in the sum and carry 0; otherwise, subtract the base from it, put the difference in the corresponding position in the sum, and carry 1. Move to the next higher position and repeat. When you run out of digits in either operand, fill in with zeroes; when you run out of digits in both operands, stop as soon as the carry is 0.

Here's how this algorithm is expressed in HP Pascal:

type
  bit: 0 .. 1;
function sum (augend, addend: natural): natural;
var
  result: natural;
    { the sum of the arguments, as it is constructed }
  augend_traverser: link;
    { a pointer to successive components of the augend }
  addend_traverser: link;
    { a pointer to successive components of the addend }
  carry: bit;
    { a carry from one digit position to the next }
  augend_digit, addend_digit: base_digits;
    { digits from corresponding positions in the augend and addend }
  place_sum: integer;
    { the sum of the digits in the same position in the augend and addend,
      plus any carry from the next lower digit position }
begin
  allocate_zero (result);
  augend_traverser := augend^.lowest;
  addend_traverser := addend^.lowest;
  carry := 0;
  while (augend_traverser <> NIL) or (addend_traverser <> NIL) or
        (carry <> 0) do begin
    if augend_traverser = NIL then
      augend_digit := 0
    else begin
      augend_digit := augend_traverser^.digit;
      augend_traverser := augend_traverser^.higher
    end;

    if addend_traverser = NIL then
      addend_digit := 0
    else begin
      addend_digit := addend_traverser^.digit;
      addend_traverser := addend_traverser^.higher
    end;
    place_sum := augend_digit + addend_digit + carry;
    if place_sum < BASE then begin
      prepend (place_sum, result);
      carry := 0
    end
    else begin
      prepend (place_sum - BASE, result);
      carry := 1
    end
  end;
  sum := result
end;
This function invokes two utility procedures that will not be exported from the module, but are present only to simplify the writing of the functions and procedures that will be exported: the allocate_zero procedure, which allocates storage for the header node of a new natural number and initializes it so that it represents 0, and the prepend procedure, which adds a digit at the high end of a natural number.

Subtraction

The subtraction algorithm has the same general structure as the addition algorithm. A borrow from the next higher column is generated whenever the current digit of the minuend is less than the current digit of the subtrahend (or even if they are equal, if the previous digit required a borrow), and BASE is then added to the minuend digit to allow the subtraction to be performed. The process stops when all the digits of the minuend have been examined.

Since the result of the subtraction may contain fewer significant digits than the minuend, whereas the algorithm generates exactly the same number of digits in the difference as in the minuend, the utility procedure strip_leading_zeroes is invoked to remove any zero digits from the result before it is returned.

The version presented here presupposes that the subtrahend is less than or equal to the minuend. In the actual naturals module, the corresponding procedure checks this precondition and signals an error if it is not met.

function difference (minuend, subtrahend: natural): natural;
var
  result: natural;
    { the difference of the arguments, as it is constructed }
  minuend_traverser: link;
    { a pointer to successive components of the minuend }
  subtrahend_traverser: link;
    { a pointer to successive components of the subtrahend }
  borrow: bit;
    { a borrow from one digit position to the next; a 0 indicates no
      borrow from the current position, a 1 indicates that there was
      a borrow }
  minuend_digit, subtrahend_digit: base_digits;
    { digits from corresponding positions in the minuend and subtrahend }
begin
  allocate_zero (result);
  minuend_traverser := minuend^.lowest;
  subtrahend_traverser := subtrahend^.lowest;
  borrow := 0;
  while minuend_traverser <> NIL do begin
    minuend_digit := minuend_traverser^.digit;
    minuend_traverser := minuend_traverser^.higher;
    if subtrahend_traverser = NIL then
      subtrahend_digit := 0
    else begin
      subtrahend_digit := subtrahend_traverser^.digit;
      subtrahend_traverser := subtrahend_traverser^.higher
    end;
    if minuend_digit - borrow < subtrahend_digit then begin
      prepend (BASE + minuend_digit - borrow - subtrahend_digit, result);
      borrow := 1
    end
    else begin
      prepend (minuend_digit - borrow - subtrahend_digit, result);
      borrow := 0
    end
  end;
  strip_leading_zeroes (result);
  difference := result
end;

Multiplication

The multiplication algorithm is the same one that is used in pencil-and-paper computation with decimal numerals: Starting from the low end of the multiplier, take one digit at a time and compute the partial product of the multiplicand and that digit; shift the partial product upwards by the number of places that come after the current digit of the multiplier. The sum of the shifted partial products is the complete product. The only difference is that on the computer it is simpler to add each shifted partial product to a running total than to save all the additions for the end. Here's the Pascal version:

function product (multiplicand, multiplier: natural): natural;
var
  result: natural;
    { the product, as it is built up from partial products }
  lower_positions: integer;
    { the number of digit positions below the current one }
  traverser: link;
    { a pointer to each successive component of the multiplier, from
      least significant to most significant }
  partial: natural;
    { a partial product: the product of the multiplicand and one digit
      of the multiplier }
  zero_tallier: integer;
    { counts off trailing zeroes as they are appended to a partial
      product }
begin
  allocate_zero (result);
  if not is_zero (multiplicand) then begin
    lower_positions := 0;
    traverser := multiplier^.lowest;
    while traverser <> NIL do begin
      if traverser^.digit <> 0 then begin
        partial :=
             multiply_natural_by_digit (multiplicand, traverser^.digit);
        for zero_tallier := 1 to lower_positions do
          append (0, partial);
        increment (result, partial);
        deallocate_natural (partial)
      end;
      lower_positions := lower_positions + 1;
      traverser := traverser^.higher
    end
  end;
  product := result
end;
A zero multiplicand is treated as a special case, so that a lot of generation and shifting of partial products that are all equal to 0 is avoided. The generation of a partial product is also skipped when a zero digit is encountered in the multiplier.

This function invokes the utility function multiply_natural_by_digit, which is not exported from the module, to compute each partial product. In this function, the result of the multiplication is accumulated in a natural number that is initialized to zero and then built up a digit at a time, starting with the least significant digit. To obtain each new digit, a digit is recovered from the multiplicand (starting at the low end) and multiplied by the entire multiplier; to this product is added a carry from the next lower digit position. The resulting ``place product'' is then divided by the base of numeration; the remainder is the digit to be prepended to the overall product, and the quotient becomes the carry into the next higher digit position. If there is a non-zero carry out of the highest digit position, it becomes the most significant digit of the result.

This algorithm exactly reflects the mechanics of ``short multiplication'' by hand. Here is the Pascal code:

function multiply_natural_by_digit (multiplicand: natural;
                                    multiplier: base_digits): natural;
var
  result: natural;
    { the product, as it is built up one digit at a time }
  traverser: link;
    { a pointer to each successive component of the multiplicand, from
      least significant to most significant }
  carry: base_digits;
    { the number of multiples of BASE to be carried into the next higher
      digit position }
  place_product: integer;
    { the product of one digit of the multiplicand and the multiplier,
      plus any carry from the next lower digit position }
begin
  allocate_zero (result);
  if multiplier <> 0 then begin
    carry := 0;
    traverser := multiplicand^.lowest;
    while traverser <> NIL do begin
      place_product := traverser^.digit * multiplier + carry;
      prepend (place_product mod BASE, result);
      carry := place_product div BASE;
      traverser := traverser^.higher
    end;
    if carry <> 0 then
      prepend (carry, result)
  end;
  multiply_natural_by_digit := result
end;
The product function also invokes two previously unseen utility procedures: append, which adds a digit at the low end of a natural number (whereas prepend attaches the digit at the high end), and deallocate_natural, which disposes of all of the storage associated with a natural number once the value of that number is no longer needed. In this case, the partial product can be deallocated once it has been added into the running total.

Furthermore, product invokes the increment procedure and the is_zero function, which are exported from the naturals module and will be discussed in their proper places below.

Division

This is the tricky one. The implementation presented here closely follows the algorithm that Per Brinch Hansen carefully develops and proves correct in ``Multiple-length division: a tour of the minefield'' (Software -- practice and experience 24 [1994], 579--601).

This algorithm finds the quotient and the remainder simultaneously. There are a fair number of cases in which the user of our natural-number library module will need both, so we might as well export the procedure that computes them together:

procedure divide (dividend, divisor: natural; var quot, rem: natural);
The separate quotient and remainder functions are easily defined with the help of divide, so the problem reduces to coding divide correctly:

function quotient (dividend, divisor: natural): natural;
var
  quot: natural;
    { the quotient resulting from the division }
  wastebasket: natural;
    { the remainder -- to be discarded }
begin
  divide (dividend, divisor, quot, wastebasket);
  deallocate_natural (wastebasket);
  quotient := quot
end;

function remainder (dividend, divisor: natural): natural;
var
  rem: natural;
    { the remainder resulting from the division }
  wastebasket: natural;
    { the quotient -- to be discarded }
begin
  divide (dividend, divisor, wastebasket, rem);
  deallocate_natural (wastebasket);
  remainder := rem
end;
There are three special cases that can be handled without any hard work: (1) Division by zero should be trapped and dismissed as an error. (2) When the dividend is less than the divisor, the quotient is zero and the remainder is the same as the dividend. (3) When the divisor contains only one digit, we can use the much simpler short-division algorithm provided by the (unexported) divide_natural_by_digit procedure:

procedure divide_natural_by_digit (dividend: natural;
                                   divisor: base_digits;
                                   var quot: natural;
                                   var rem: base_digits);
var
  traverser: link;
    { points successively to the digits of the dividend, from most
      significant to least significant }
  short_dividend: integer;
    { a partial dividend, composed of the remainder left over from the
      division in the previous position, scaled up by a factor of BASE,
      plus the digit in the current position }
begin
  assert (divisor <> 0, DIVISION_EXCEPTION, natural_handler);
  allocate_zero (quot);
  traverser := dividend^.highest;
  if traverser = NIL then
    rem := 0
  else if traverser^.digit < divisor then begin
    rem := traverser^.digit;
    traverser := traverser^.lower
  end
  else
    rem := 0;
  while traverser <> NIL do begin
    short_dividend := rem * BASE + traverser^.digit;
    append (short_dividend div divisor, quot);
    rem := short_dividend mod divisor;
    traverser := traverser^.lower
  end
end;
This procedure begins by trapping divisions by zero. It then initializes the quotient to zero and gets ready to recover the first digit of the quotient. The quotient will be one digit shorter than the dividend if, and only if, the first digit of the dividend is less than the divisor, so the procedure tests to see whether this is the case; if so, it suppresses the leading zero of the quotient, arranges for the remainder of the first-digit division to be equal to the first digit of the divisor, and then enters the main division loop with traverser pointing to the second digit of the divisor. This ensures that the first pass through the loop will generate a non-zero leading digit for the quotient.

On each iteration of the division loop, the procedure constructs a ``short dividend,'' performs the short division, attaches the quotient (which will always be a single digit) to the low end of quot, and changes rem to reflect the remainder of the division. When the loop terminates, all the digits of the quotient will have been obtained, and the final remainder is the remainder for the entire division.

Here, then, is the general structure for the divide procedure:

procedure divide (dividend, divisor: natural; var quot, rem: natural);
var
  irem: base_digits;
    { the remainder of a short division -- used only if divisor is a
      single digit }
begin
  assert (not is_zero (divisor), DIVISION_EXCEPTION, natural_handler);
  if less (dividend, divisor) then begin
    allocate_zero (quot);
    assign (rem, dividend)
  end
  else if divisor^.size <= 1 then begin
    divide_natural_by_digit (dividend, divisor^.highest^.digit,
                             quot, irem);
    rem := integer_to_natural (irem)
  end
  else
    { Do the really difficult case in which the divisor has at least
      two digits and the dividend is not less than the divisor. }
end;
This procedure invokes less, assign, and integer_to_natural, which will be presented below.

In pencil-and-paper computation, we begin the ``really difficult case'' by guessing the first digit of the quotient, multiplying that digit by the divisor, and subtracting the result from the first few digits of the dividend. If the result of the subtraction is either negative or greater than the divisor, we know that we've guessed wrong, and we back up and make another guess.

Computers cannot guess, so we need to automate the process. The brute-force method would be to initialize a variable trial_digit to BASE - 1 and then decrement it as long as the result of multiplying it by the divisor is greater than the first few digits of the dividend. This works quite well if BASE is 2, but it's terribly inefficient when BASE is large, as it is here.

A better approach is to construct another, much simpler division problem in which the first digit of the quotient is the same as in ours. For instance, if we take the first three digits of the dividend and treat them as if they were a separate number first_three (which will be less than or equal to MAXINT -- we chose our base of numeration precisely to ensure this), and similarly split off the first two digits of the divisor to form the number first_two, then dividing first_three by first_two should yield a quotient in which the first digit is a close approximation of the first digit of the original division problem. In fact, Brinch Hansen shows by elementary algebra that the trial digit obtained in this way is either correct or, at worst, one unit too large.

The cases in which the trial digit is even one unit too large are expensive, however, since correcting the approximation involves discarding all the work that was done in constructing the product of the multiplicand and the trial digit. It turns out that the frequency of an erroneous trial digit decreases as the value of first_two increases, since the relative error involved in discarding the subsequent digits becomes less. A convenient way to make first_two large is to begin the entire division process by normalizing the dividend and the divisor -- scaling them up by the same (one-digit integer) factor. This obviously will not affect the quotient at all; and, although it will have the effect of scaling up the remainder by the same factor, we can easily scale it back down again at the end of the division process.

The scaling factor is computed as BASE div (first + 1), where first is the leading digit of the original divisor. Before scaling, a leading zero is prepended to the dividend; this ensures that the quotient obtained from the first three digits of the scaled dividend and the first two of the scaled divisor will always be a single digit. (Actually, it is still possible for the quotient to be BASE, in the rare case that the correct digit is BASE - 1 and the quotient is too high in spite of the normalization. The function that we will use to determine the trial digit will test for this case and correct it by subtracting one before returning the quotient.)

Unfortunately, it is possible for the trial digit computed from the first three digits of the scaled dividend to be 0, so we may still have to remove a leading zero from the quotient at the end of the division process.

The procedure for scaling up a natural number is similar to the algorithm used in the multiply_natural_by_digit procedure, except that it destroys or overwrites the original natural number:

procedure scale_up_by_digit (var given: natural; scale: base_digits);
var
  traverser: link;
    { a pointer to each successive component of given, from least
      significant to most significant }
  carry: base_digits;
    { the number of multiples of BASE to be carried into the next
      higher digit position }
  place_product: integer;
    { the product of one digit of given and the scale, plus any carry
      from the next lower digit position }
begin
  if scale = 0 then begin
    deallocate_natural (given);
    allocate_zero (given)
  end
  else begin
    carry := 0;
    traverser := given^.lowest;
    while traverser <> NIL do begin
      place_product := traverser^.digit * scale + carry;
      traverser^.digit := place_product mod BASE;
      carry := place_product div BASE;
      traverser := traverser^.higher
    end;
    if carry <> 0 then
      prepend (carry, given)
  end
end;
The find_trial_digit function is now easily coded. It begins with the computation of first_three as an integer; since first_two will be the same throughout the division, it is computed once and for all by the enclosing divide procedure and treated here as a global identifier. Then the trial digit is obtained by division. If the result of the division is BASE, the correct trial digit must be BASE - 1, so the corrected value is returned instead. Here it is in Pascal:

function find_trial_digit (residue: natural): base_digits;
var
  first_three: integer;
    { the value of the first three digits of residue; it will always have
      at least three, because the scaled divisor has at least two and
      residue always has one more than the scaled divisor }
  traverser: link;
    { a pointer to successive components of residue, starting with the
      most significant digit }
  position: integer;
    { counts off digits in residue }
  trial: integer;
    { an approximate value for the next quotient digit }
begin
  first_three := 0;
  traverser := residue^.highest;
  for position := 1 to 3 do begin
    first_three := first_three * BASE + traverser^.digit;
      traverser := traverser^.lower
  end;
  trial := first_three div first_two;
  if trial = BASE then
    find_trial_digit := BASE - 1
  else
    find_trial_digit := trial
end;
With the help of the scale_up_by_digit procedure and the find_trial_digit function, we can now draw up the plan for the really hard case: (1) Compute the appropriate scaling factor. (2) Attach a leading zero to the dividend and scale it up; scale up the divisor. (3) Initialize the variable (call it residue) that will hold the appropriate number of digits of the scaled dividend by copying into it one more digit than the scaled divisor has. (4) Call find_trial_digit to obtain a good estimate of the next digit of the quotient. (5) Multiply that trial digit by the scaled divisor; if the result exceeds the value of residue, decrease the trial digit by 1 at multiply again. (6) Attach the trial digit at the low end of the quotient and subtract the product of the trial digit and the scaled divisor from the value of residue. (7) If there are any remaining digits in the scaled dividend, attach the first of them at the low end of the residue and repeat steps 4-7. (8) Otherwise, strip any leading zeroes from the quotient and divide the value of residue by the scaling factor to obtain the correct remainder for the original division.

Here's how it looks in Pascal:

begin

  { Compute the appropriate scaling factor. }

  scaling_factor := BASE div (divisor^.highest^.digit + 1);

  { Attach a leading zero to the dividend and scale it up; scale up the
    divisor. }

  assign (scaled_dividend, dividend);
  prepend (0, scaled_dividend);
  scale_up_by_digit (scaled_dividend, scaling_factor);
  assign (scaled_divisor, divisor);
  scale_up_by_digit (scaled_divisor, scaling_factor);

  { Compute the value of the first two digits of the scaled divisor,
    to be used in computing trial digits for the quotient. }

  first_two := BASE * scaled_divisor^.highest^.digit +
               scaled_divisor^.highest^.lower^.digit;

  { Initialize residue by copying into it one more digit than the
    scaled divisor has. }

  allocate_zero (residue);
  traverser := scaled_dividend^.highest;
  for position := 1 to scaled_divisor^.size + 1 do begin
    append (traverser^.digit, residue);
    traverser := traverser^.lower
  end;

  { Construct the quotient one digit at a time, from most significant
    to least significant. }

  allocate_zero (quot);
  finished := FALSE;
  while not finished do begin

    { Call find_trial_digit to obtain a good estimate of the next
      digit of the quotient. }

    trial_digit := find_trial_digit (residue);

    { Multiply that trial digit by the scaled divisor; if the result
      exceeds the value of residue, decrease the trial digit by 1 at
      multiply again. }

    trial_product :=
        multiply_natural_by_digit (scaled_divisor, trial_digit);
    strip_leading_zeroes (residue) { so that the less function will work }
    if less (residue, trial_product) then begin
      trial_digit := trial_digit - 1;
      deallocate_natural (trial_product);
      trial_product :=
          multiply_natural_by_digit (scaled_divisor, trial_digit);
    end;

    { Attach the trial digit at the low end of the quotient and
      subtract the product of the trial digit and the scaled divisor
      from the value of residue.  Ensure that residue has the correct
      number of digits. }

    append (trial_digit, quot);
    decrement (residue, trial_product);
    deallocate_natural (trial_product);
    while residue^.size < scaled_divisor^.size do
      prepend (0, residue);

    { If there are any remaining digits in the scaled dividend,
      attach the first of them at the low end of the residue and repeat
      the loop. }

    if traverser = NIL then
      finished := TRUE
    else begin
      append (traverser^.digit, residue);
      traverser := traverser^.lower
    end

  end;

  { Strip any leading zeroes from the quotient and divide the value of
    residue by the scaling factor to obtain the correct remainder for
    the original division. } 

  strip_leading_zeroes (quot);
  deallocate_natural (scaled_dividend);
  deallocate_natural (scaled_divisor);
  strip_leading_zeroes (residue);
  divide_natural_by_digit (residue, scaling_factor, rem, wastebasket);
  deallocate_natural (residue)

end
So that's how division works.

Exponentiation

To raise a number to a power, one approach would be to use repeated multiplication by the base, but this is unpleasantly slow when the exponent is large. To reduce the number of intermediate results, we can use two laws of exponents: y^(2x) = (y^2)^x and y^(2x + 1) = y x (y^2)^x. Each of these laws shows a way to compute the result of raising a given base y to a large power by computing a larger base y^2 raised to a smaller power. (We use the first law to reduce even exponents, the second to reduce odd ones.) By repeating the process, we can always reduce the exponent to 0, at which point the result is known to be 1 regardless of the value of the base. This suggests the following recursive implementation:

function power (base, exponent: natural): natural;
begin
  if is_zero (exponent) then
    power := integer_to_natural (1)
  else if is_even (exponent) then
    power := power (square (base), half (exponent))
  else
    power := product (base, power (square (base), half (exponent)))
end;
As soon as one considers how to recycle the storage used by this function, however, it becomes apparent that using recursion is going to require a lot of storage if either the base or the exponent is at all large, and the recursion aggravates the problem by retaining all of the storage for intermediate results until the last recursive call is reached. The key difficulty is that the multiplications performed in the various calls to product cannot be performed until after the recursive calls to power have produced the multiplier. However, since multiplication is commutative, it actually would not matter whether these multiplications were done in reverse order, as the recursive calls are exiting, or in forwards order, as the exponent is being reduced; in either case, the current value of the base will be a factor of the product if, and only if, the current exponent is odd. But if the multiplications are performed on the way into each recursive call, then the multiplicand can be discarded -- or, better yet, overwritten -- as soon as its value has been used for the last time.

This reflection suggests the following alternative structure, in which the recursive function takes a third argument, accumulator, to hold the product of all of the factors of the final result that have so far been encountered:

function power (base, exponent: natural): natural;
var
  copy_of_base, copy_of_exponent: natural;
    { duplicates of the base and exponent that can be changed destructively
      during the execution of this function }

  function power_helper (base, exponent, accumulator: natural): natural;
  begin
    if is_zero (exponent) then
      power_helper := accumulator
    else begin
      if is_odd (exponent) then
        scale_up (accumulator, base);
      scale_up (base, base);
      halve (exponent);
      power_helper := power_helper (base, exponent, accumulator)
    end
  end;

begin { function power }
  assign (copy_of_base, base);
  assign (copy_of_exponent, exponent);
  power := power_helper (copy_of_base, copy_of_exponent,
                         integer_to_natural (1));
  deallocate_natural (copy_of_base);
  deallocate_natural (copy_of_exponent)
end;
Note now that the inner function power_helper is ``tail-recursive'': When it receives the value of a recursive call, it simply returns it, without performing any further computation on it. It is particularly simple to convert a tail-recursive function to an iterative form, and in Pascal the resulting function will generally run faster and consume less memory than the recursive version. Here is the iterative form of the algorithm:

function power (base, exponent: natural): natural;
var
  copy_of_base, copy_of_exponent: natural;
    { duplicates of the base and exponent that can be changed destructively
      during the execution of this function }
  accumulator: natural;
    { the product of various powers of the base }
begin
  assign (copy_of_base, base);
  assign (copy_of_exponent, exponent);
  accumulator := integer_to_natural (1);
  while not is_zero (copy_of_exponent) do begin
    if is_odd (copy_of_exponent) then
      scale_up (accumulator, copy_of_base);
    scale_up (copy_of_base, copy_of_base);
    halve (copy_of_exponent)
  end;
  deallocate_natural (copy_of_base);
  deallocate_natural (copy_of_exponent);
  power := accumulator
end;
All of these versions of power return a result of 1 when given a base of 0 and an exponent of 0. This accords with the usual combinatorial interpretation of the expression 0^0. Some mathematicians regard this value as undefined, since the limiting value of y^0 as y approaches 0 is 1, while the limiting value of 0^x as x approaches 0 is 0; but this line of reasoning, based on the continuity of the real numbers, seems rather less forceful when we are considering only natural numbers. It would be easy enough to treat this special case as an error, but there isn't any harm in letting it pass, so long as the function's behavior is well documented.

The next few functions are special cases of these basic arithmetic operations, and could be coded as calls to the corresponding functions with one operand fixed. However, since the fixed operands are small, we can write much faster versions of the specialized functions.

Successor and predecessor

The idea here is to copy the operand digit by digit, starting at the low end and propagating a carry exactly as far as necessary. If the carry is propagated all the way through the operand, an extra place must be added to the result.

function successor (given: natural): natural;
var
  carry: bit;
    { initially, the 1 that must be added; subsequently, a 1 so long as
      a carry must be propagated, and 0 thereafter }
  result: natural;
    { the successor natural number as it is constructed, digit by digit }
  traverser: link;
    { a pointer to successive components of the given natural number,
      from least significant to most significant }
begin
  carry := 1;
  allocate_zero (result);
  traverser := given^.lowest;
  while traverser <> NIL do begin
    if carry = 0 then
      prepend (traverser^.digit, result)
    else if traverser^.digit = BASE - 1 then
      prepend (0, result)
    else begin
      prepend (traverser^.digit + 1, result);
      carry := 0
    end;
    traverser := traverser^.higher
  end;
  if carry = 1 then
    prepend (1, result);
  successor := result
end;
The algorithm for the predecessor function is similar, but it differs in three important ways: (1) It must test the precondition that the given natural number is non-zero; (2) a borrow is propagated instead of a carry; and (3) instead of testing whether a new leading digit must be added, the function must check whether the leading digit of the result has been reduced to zero and remove it if it has.

The twice and half functions

Finding the double of a given natural number is quite similar to multiplying a natural number by a digit (as in the procedure multiply_natural_by_digit, above), except for a few small optimizations resulting from the fact that the carry is known to be either zero or one.

function twice (given: natural): natural;
var
  result: natural;
    { the double of the given natural number, as it is constructed a
      digit at a time }
  carry: bit;
    { indicates whether a carry was generated by the preceding
      digit-doubling }
  traverser: link;
    { a pointer to successive components of the given natural number,
      from least significant to most significant }
  place_product: integer;
    { the double of one digit of the given natural number, plus any carry
      from the next lower digit position } 
begin
  allocate_zero (result);
  carry := 0;
  traverser := given^.lowest;
  while traverser <> NIL do begin
    place_product := traverser^.digit * 2 + carry;
    if place_product < BASE then begin
      prepend (place_product, result);
      carry := 0
    end
    else begin
      prepend (place_product - BASE, result);
      carry := 1
    end;
    traverser := traverser^.higher
  end;
  if carry = 1 then
    prepend (1, result);
  twice := result
end;
Similarly, half resembles divide_natural_by_digit, except for small optimizations.

The square and cube functions

The most straightforward way to do these is simply to multiply:

function square (given: natural): natural;
begin
  square := product (given, given)
end;

function cube (given: natural): natural;
var
  sq: natural;
    { the square of the given number }
begin
  sq := product (given, given);
  cube := product (sq, given);
  deallocate_natural (sq)
end;

Destructive arithmetic

The procedures in the next group -- increment, decrement, scale_up, scale_down, up1, down1, double, and halve -- are the destructive versions of sum, difference, product, quotient, successor, predecessor, twice, and half, respectively. The first two and the last four are almost identical to their non-destructive counterparts, the only difference being that digits are stored back into one of the operands instead of being attached to a result variable. The scale_up and scale_down procedures are even more derivative; they simply compute the product or quotient of their operands and then replace one operand with the result, disposing of its original value.

In coding the increment and decrement procedures, one should give careful consideration to what will happen when the procedure is invoked with the same natural number in both argument positions.

To illustrate the actual code, here are the increment and scale_up procedures:

procedure increment (var given: natural; amount: natural);
var
  given_traverser: link;
    { a pointer to successive components of the natural number to be
      incremented }
  amount_traverser: link;
    { a pointer to successive components of the increment }
  carry: bit;
    { a carry from one digit position to the next }
  given_digit, amount_digit: base_digits;
    { digits from corresponding positions in given and amount }
  place_sum: integer;
    { the sum of the digits in the same position in given and amount,
      plus any carry from the next lower digit position }
begin
  given_traverser := given^.lowest;
  amount_traverser := amount^.lowest;
  carry := 0;
  while (given_traverser <> NIL) or (amount_traverser <> NIL) or
        (carry <> 0) do begin
    if given_traverser = NIL then
      given_digit := 0
    else
      given_digit := given_traverser^.digit;
    if amount_traverser = NIL then
      amount_digit := 0
    else begin
      amount_digit := amount_traverser^.digit;
      amount_traverser := amount_traverser^.higher
    end;
    place_sum := given_digit + amount_digit + carry;
    if place_sum < BASE then
      carry := 0
    else begin
      place_sum := place_sum - BASE;
      carry := 1
    end;
    if given_traverser = NIL then
      prepend (place_sum, given)
    else begin
      given_traverser^.digit := place_sum;
      given_traverser := given_traverser^.higher
    end
  end
end;

procedure scale_up (var given: natural; scale: natural);
var
  delendum: natural;
    { a copy of the pointer to the header node of the old value of given }
begin
  delendum := given;
  given := product (given, scale);
  deallocate_natural (delendum)
end;

Comparison functions

Since the header node for a natural number includes a count of the number of digits in that number (that is, the number of components in the doubly-linked list), one can make a first cut at comparing two numbers by comparing their digit counts, proceeding to examine the digit values only in the special case where the two numbers are equal in length; this considerably simplifies the coding of the traversals of the doubly-linked lists.

For example, in the less function, the first operand must be less than the second if it contains fewer digits, and cannot be less than the second if it contains more. In the remaining case, the function can simply start at the high end of both numbers and compare corresponding digits until either they fail to match or both lists are exhausted.

Here's what it looks like in Pascal:

function less (first, second: natural): Boolean;
var
  first_traverser, second_traverser: link;
    { pointers to corresponding components of first and second, starting
      with the most significant digits }
  finished: Boolean;
    { indicates whether the value to be returned has been conclusively
      established yet }
begin
  if first^.size < second^.size then
    less := TRUE
  else if second^.size < first^.size then
    less := FALSE
  else begin
    first_traverser := first^.highest;
    second_traverser := second^.highest;
    finished := FALSE;
    repeat
      if first_traverser = NIL then begin
        less := FALSE;
        finished := TRUE
      end
      else if first_traverser^.digit < second_traverser^.digit then begin
        less := TRUE;
        finished := TRUE
      end
      else if second_traverser^.digit < first_traverser^.digit then begin
        less := FALSE;
        finished := TRUE
      end
      else begin
        first_traverser := first_traverser^.lower;
        second_traverser := second_traverser^.lower;
      end
    until finished
  end
end;
The equality test is somewhat similar, but simpler, since when an inequality is detected it is not necessary to determine which operand is smaller:

function equal (first, second: natural): Boolean;
var
  first_traverser, second_traverser: link;
    { pointers to corresponding components of first and second, starting
      with the most significant digits }
  finished: Boolean;
    { indicates whether the value to be returned has been conclusively
      established yet }
begin
  if first^.size <> second^.size then
    equal := FALSE
  else begin
    first_traverser := first^.highest;
    second_traverser := second^.highest;
    finished := FALSE;
    repeat
      if first_traverser = NIL then begin
        equal := TRUE;
        finished := TRUE
      end
      else if first_traverser^.digit <> second_traverser^.digit then begin
        equal := FALSE;
        finished := TRUE
      end
      else begin
        first_traverser := first_traverser^.lower;
        second_traverser := second_traverser^.lower;
      end
    until finished
  end
end;
The other comparison functions differ from these only in the placement of TRUE and FALSE.

The is_zero function

In the special case of comparison with zero, there is an easy optimization: A natural number is equal to zero if, and only if, its doubly-linked list has no components.

function is_zero (given: natural): Boolean;
begin
  is_zero := (given^.size = 0)
end;

The max and min functions

These functions use the obvious algorithm:
function max (first, second: natural): natural;
begin
  if not_less (first, second) then
    max := first
  else
    max := second
end;
The min function is similar.

The is_multiple function

The basic idea is to determine whether dividing the first operand by the second leaves a remainder of zero. The special case in which the second operand is zero is split off and handled separately before the division is attempted.

function is_multiple (first, second: natural): Boolean;
var
  rem: natural;
    { the remainder when first is divided by second }
begin
  if is_zero (second) then
    is_multiple := is_zero (first)
  else begin
    rem := remainder (first, second);
    is_multiple := is_zero (rem);
    deallocate_natural (rem)
  end
end;
Once again, note that this function is careful to recycle the storage associated with the natural number that it generates. There is a temptation to write the else-clause simply as

is_multiple := is_zero (remainder (first, second))
but this would create a memory leak. The storage allocated for the remainder could never be deallocated, because no pointer to it is retained.

Parity functions

Since the base of numeration is even, one can tell whether a given natural number is even or odd by looking at its last digit. (The other digits signify multiples of positive powers of the base and therefore contribute only even values to the number represented; only the last digit can contribute an even value or an odd one.)

Here is the Pascal code for is_even; the is_odd function is similar. Note that, since zero has no last digit (in our implementation), it must be handled as a separate case.

function is_even (given: natural): Boolean;
begin
  if given^.size = 0 then
    is_even := TRUE
  else
    is_even := not odd (given^.lowest^.digit)
end;

Input and output procedures

The read_natural procedure reads in decimal digits one by one from a text file, continuing until either the end of the file or a non-digit character is reached. It presupposes that the text file has already been opened for input and that there is at least one decimal digit to be read.

A variable that will hold the natural number that is being read in is initialized to zero at the beginning of the process. Then, as each digit is read in, the value of this variable is scaled up by a factor of ten and incremented by the decimal value of that digit.

Pascal's numeric input routines tacitly skip over any whitespace characters (tabs, newlines, vertical tabs, form feeds, carriage returns, and spaces) that precede the first digit. For consistency, and as an aid to the programmer, the read_natural procedure also consumes such characters, by invoking the skip_white_space procedure:

procedure skip_white_space (var source: text);
const
  TAB = #I;
  NEWLINE = #J;
  VERTICAL_TAB = #K;
  FORMFEED = #L;
  RETURN = #M;
  SPACE = #32;
var
  finished: Boolean;
    { indicates whether it is necessary to keep looking for white space
      to skip }
begin
  finished := FALSE;
  while not finished do
    if eof (source) then
      finished := TRUE
    else if source^ in [TAB, NEWLINE, VERTICAL_TAB, FORMFEED, RETURN,
                        SPACE] then
      get (source)
    else
      finished := TRUE
end;
The odd-looking syntax in the const definitions is HP Pascal's way of providing literals that designate control characters.

The read_natural procedure looks more or less like this:

procedure read_natural (var source: text; var legendum: natural);
var
  finished: Boolean;
    { indicates whether all of the digits of the numeral have been read
      in }
begin
  skip_white_space (source);
  allocate_zero (legendum);
  finished := FALSE;
  repeat
    scale_up_by_digit (legendum, 10);
    increment_by_digit (legendum, ord (source^) - ORD_ZERO);
    get (source);
    if eof (source) then
      finished := TRUE
    else
      finished := ((source^ < '0') or ('9' < source^))
  until finished
end;
The code in the naturals module includes tests to make sure that there are actually digits to be read.

The input_natural procedure is implemented by a call to read_natural, with the text file argument specialized to input.

The main difficulty with the write_natural procedure is that it is easiest to compute the decimal digits of the number to be written out in reverse order -- least significant digit first -- while the first digit that is written out must be the most significant digit. To cope with this problem, the naturals module imports another module, stacks.

A stack is a data structure that can receive and store any number of values of a single data type, in this case integer, and can recover and return those values one by one, subject to the constraint that the only value that can be recovered from the stack at any given time is the one that was most recently added to the stack. The write_natural procedure uses four routines from the stacks module: initialize_stack, which assigns an empty stack to its argument; push_to_stack, which stores a given integer value in a given stack; pop_stack, which recovers an integer value from a given stack and returns it; and is_empty_stack, which determines whether a given stack is empty.

The plan of the write_natural procedure is to create an empty stack and then to repeatedly divide the given natural number by ten, pushing each remainder on the stack as it is recovered. The successive remainders are the digits of the decimal numeral for the given natural number, from least significant to most significant. Once the natural number has been reduced to zero by these successive divisions, the output phase begins: The stack is popped repeatedly until it is empty, and each recovered remainder is written out as a one-digit number.

As usual, zero must be handled as a special case, since the method just described would print nothing at all.

Here's the Pascal code:

procedure write_natural (var target: text; scribendum: natural);
var
  digits: stack;
    { It is easiest to recover the digits of the number to be printed
      least significant first.  Consequently, we push them onto this
      stack as they are recovered, then pop them off this stack to
      print them one by one. }
  working: natural;
    { a throwaway copy of scribendum; later, the part of the number not
      yet reduced to decimal digits }
  quot: natural;
    { the quotient resulting from a division of working by 10 }
  rem: base_digits;
    { the remainder resulting from such a division }
begin
  if is_zero (scribendum) then
    write (target, '0')
  else begin
    initialize_stack (digits);
    assign (working, scribendum);
    while not is_zero (working) do begin
      divide_natural_by_digit (working, 10, quot, rem);
      deallocate_natural (working);
      working := quot;
      push_to_stack (rem, digits)
    end;
    deallocate_natural (working);
    while not is_empty_stack (digits) do
      write (target, pop_stack (digits) : 1)
  end
end;
The output_natural and output_natural_to_standard_error procedures are implemented by calls to write_natural, with the text file argument specified as output or stderr, respectively.

Conversion to and from strings

The string_to_natural function and the natural_to_string procedure are similar to the input and output routines, except that the characters are extracted from or assigned to positions in the string rather than text files:

function string_to_natural (var s: string): natural;
var
  result: natural;
    { the natural number expressed by s, constructed by evaluating s
      digit by digit }
  position: integer;
    { counts off the character positions in s }
begin
  allocate_zero (result);
  for position := 1 to strlen (s) do begin
    scale_up_by_digit (result, 10);
    increment_by_digit (result, ord (s[position]) - ORD_ZERO)
  end;
  string_to_natural := result
end;

procedure natural_to_string (n: natural; var s: string);
var
  digits: stack;
    { It is easiest to recover the digits of the number least significant
      first.  Consequently, we push them onto this stack as they are
      recovered, then pop them off this stack to store them from left
      to right in s. }
  working: natural;
    { a throwaway copy of n; later, the part of the number not
      yet reduced to decimal digits }
  quot: natural;
    { the quotient resulting from a division of working by 10 }
  rem: base_digits;
    { the remainder resulting from such a division }
  digit_count: integer;
    { tallies the number of digits in the decimal numeral for n }
  position: integer;
    { counts off the character positions in s }
begin
  if is_zero (n) then begin
    setstrlen (s, 1);
    s[1] := '0'
  end
  else begin
    initialize_stack (digits);
    digit_count := 0;
    assign (working, n);
    while not is_zero (working) do begin
      divide_natural_by_digit (working, 10, quot, rem);
      deallocate_natural (working);
      working := quot;
      push_to_stack (rem, digits);
      digit_count := digit_count + 1
    end;
    deallocate_natural (working);
    setstrlen (s, digit_count);
    for position := 1 to digit_count do
      s[position] := chr (ORD_ZERO + pop_stack (digits));
  end
end;

The assign procedure

The plan for this procedure is to initialize the target variable to zero and then append the digits of the number to be assigned, one by one, to the value of the target variable:

procedure assign (var target: natural; source: natural);
var
  traverser: link;
    { a pointer to each successive digit of source }
begin
  allocate_zero (target);
  traverser := source^.highest;
  while traverser <> NIL do begin
    append (traverser^.digit, target);
    traverser := traverser^.lower
  end
end;

Transfer functions

To convert an integer to a natural number, repeatedly divide it by BASE and add each successive remainder at the front of the natural number:

function integer_to_natural (n: integer): natural;
var
  result: natural;
    { the natural number of value equal to n, constructed digit by digit
      through successive divisions of n }
begin
  allocate_zero (result);
  while n <> 0 do begin
    prepend (n mod BASE, result);
    n := n div BASE
  end;
  integer_to_natural := result
end;
To convert a natural number to an integer, initialize a result variable to zero, then take up its digits one at a time, from most significant to least significant; at each digit, multiply the current result by BASE and add the value of the current digit:

function natural_to_integer (n: natural): integer;
var
  result: integer;
    { the integer of value equal to n, constructed by evaluating n
      digit by digit }
  traverser: link;
    { a pointer to each successive digit of n, from most significant to
      least significant }
begin
  result := 0;
  traverser := n^.highest;
  while traverser <> NIL do begin
    result := result * BASE + traverser^.digit;
    traverser := traverser^.lower
  end;
  natural_to_integer := result
end;
In the actual implementation below, each of these functions contains calls to the assert procedure to enforce its preconditions.

Additions

(1) While testing and debugging the naturals module, I found it helpful to be able to invoke a procedure (debug_output_natural) that displayed the internal structure of natural numbers. There is an argument for including this procedure in the public interface, even though in a way it breaks the abstraction boundary, because it might allow the user of the library to supply more informative bug reports.

(2) Although the library procedures are themselves careful to recycle any storage they allocate for natural numbers that are used only internally, we have not yet provided any way for the calling program to declare that it has finished with a natural number and to deallocate the storage it occupies.

Ideally, the run-time system that is linked to executable programs would provide a garbage collector, that is, a routine that would keep track of allocated storage and recycle any pieces of it that the program was no longer able to access. Many programming languages (such as Common Lisp, Icon, and Java) have a garbage collector, but Pascal does not. The alternative is to add the deallocate_natural procedure to the interface, requiring (or rather inviting) programmers to invoke it every time they want to dispose of a natural number. Unfortunately, this makes many computations much clumsier to write. For example, if one wants to compute and display the value of ax + by, where a, x, b and y are natural numbers, one would like to be able to write

output_natural (sum (product (a, x), product (b, y)))
But without automatic garbage collection, one must instead write

temp1 := product (a, x);
temp2 := product (b, y);
temp3 := sum (temp1, temp2);
deallocate_natural (temp1);
deallocate_natural (temp2);
output_natural (temp3);
deallocate_natural (temp3)
This is a strong argument for garbage collection.

With this introduction, you're now ready to read the naturals module, in which all the procedures and functions are spelled out completely. The major difference in the code is that the preconditions of the various procedures and functions, which are simply taken for granted in this handout, are carefully tested, and errors are reported when those preconditions are violated.


This document is available on the World Wide Web as

http://www.math.grin.edu/~stone/courses/fundamentals/bignums.html

created January 24, 1996
last revised February 14, 1996