Numeration

Numbers and numerals

In a way, working in a programming language makes it easy to distinguish between a number (a value of a type like Integer or Real in Pascal) and a numeral expressing that number (which would be a character string in Pascal). If you keep this distinction in mind, you won't find it surprising that there can be many different numerals for the same number (for instance, `35.2', `+0035.200', and `3.52 x 10^1' are all numerals for the number 35.2), and that the same numeral can stand for different numbers in different systems of numeration (for instance, `11100' stands for the number eleven thousand one hundred in decimal numeration, but for the number twenty-eight in binary numeration).

What tends to confuse people is that Pascal itself requires the programmer to use decimal numerals when referring to numbers inside programs. Moreover, the predefined input procedures expect decimal numeration to be used at the keyboard and in any text files from which numbers are to be read in, during the execution of the program; similarly, whenever numbers are written out to the monitor or to a text file, they are expressed in decimal numeration. However, these features of Pascal are completely conventional and are simply designed to cater to the arbitrary preferences of the many programmers and users who happen to have ten fingers. Computers, which have no fingers, use a completely different method of representing numbers; Pascal conceals this from programmers by giving no indication of how complicated the input and output procedures for integers and reals really are.

Evaluating a numeral

To get some idea of what is involved, let's consider first a procedure that takes a string of digit characters and determines what number it expresses in decimal numeration:

procedure Evaluate (Numeral: packed array [Low .. High: Integer] of Char;
                    var Value: Integer); 
var
  Index: Integer;
begin
  Value := 0;
  for Index := Low to High do
    Value := Value * 10 + Ord (Numeral[Index]) - Ord ('0')
end;
Here I've used a conformant array parameter for the numeral, so as to be able to use the same procedure for numerals of any number of digits.

The parameter Value constantly contains the numeric value of the part of the numeral that the procedure has so far inspected. It is initialized to 0, and then the procedure traverses the numeral from left to right. As it encounters each new digit, it multiplies the value of the previous part of the numeral by ten (since each digit in that previous part of the numeral is now one position further to the left and consequently has a positional value ten times as great) and adds the value of the new digit, which can be determined by measuring its distance from the digit 0 in the character set.

The appearance of the numeric literal 10 in this procedure suggests that it could be usefully generalized by parameterizing it for different bases. The same method can be used to evaluate binary and octal numerals, for instance:

procedure Evaluate (Numeral: packed array [Low .. High: Integer] of Char;
                    var Value: Integer; Base: Integer)
var
  Index: Integer;
begin
  Value := 0;
  for Index := Low to High do
    Value := Value * Base + Ord (Numeral[Index]) - Ord ('0')
end;
Although the procedure shown above demonstrates the basic logic of evaluation, it is not yet suitable for general use, because it works only under certain conditions that it does not enforce. It presupposes that the base is between 2 and 10; that the numeral that it is given is a non-empty string containing only digits that are valid for that base; and that the value of the numeral can be represented in the Integer type. Among the base-conversion routines at the end of this handout is a more elaborate version of Evaluate that tests to make sure that these preconditions are met. It also extends the evaluation mechanism to bases in the range from 11 to 36, treating the letters of the alphabet as ``digits'' in these higher bases. The ``digit'' A has the value ten when used in a numeral of one of these higher bases, B has the value eleven, and so on.

Pascal's Read and ReadLn procedures perform essentially the same algorithm when reading in the value of an integer variable, because they too have to recover that value from a sequence of characters that is either typed at the keyboard or recovered from a text file.

Expressing a natural number

When it is necessary to express the value of an integer variable in human-readable form, as a sequence of digits, the opposite conversion takes place. It's a little trickier, since the easiest way of recovering the digits (by successive divisions by 10) produces them from right to left, whereas we want to put them into the string from left to right -- if we try to place the rightmost digit first, we won't know what position in the string it should be placed at. The solution is to use a recursive procedure that counts the digits on the way in and places them appropriately on the way out:

procedure Express (Value: Integer;
                   var Numeral: packed array [Low .. High: Integer] of Char);
var
  Length: Integer;

  procedure Helper (Value: Integer; DigitsSoFar: Integer;
                    var Position: Integer);
  begin
    if Value < 10 then begin
      Position := 1;
      Numeral[Position] := Chr (Ord ('0') + Value)
    end
    else begin
      Helper (Value div Base, DigitsSoFar + 1, Position);
      Position := Position + 1;
      Numeral[Position] := Chr (Ord ('0') + Value mod 10)
    end
  end;

begin { procedure Express }
  Helper (Value, 0, Length)
end;
On each successive call to Helper, the first parameter (Value) will be ten times smaller, and DigitsSoFar will be one larger. Eventually Value must be reduced to an integer less than ten, and at that point Position will be initialized to 1 and the first digit will be placed at the left end of Numeral. When a recursive call is finished and control is returned to the next lower level, Position is incremented and the next digit of the numeral is appropriately placed.

Again, this procedure should be reworked to include precondition tests (Value should be non-negative, and there should be enough character positions in Numeral to hold all the digits of the numeral) and generalized to allow any base from 2 to 36. The version at the end of the handout shows how this is done.

Converting from one base of numeration to another

Once we have both Evaluate and Express in generalized form, it becomes trivial to convert any non-negative integer (less than or equal to MaxInt) from one base of numeration to another:

procedure BaseConvert (NumeralIn: packed array [LowIn .. HighIn] of Char;
                       InputBase: Integer; OutputBase: Integer;
                       var NumeralOut: packed array [LowOut .. HighOut]
                                                                of Char);
var
  Value: Integer;
begin
  Evaluate (NumeralIn, Value, InputBase);
  Express (Value, NumeralOut, OutputBase)
end;
In other words, see what integer is expressed by the numeral you start with in the original system of numeration, then express that numeral in the desired system of numeration.

Base-conversion procedures

Here are the fully-developed versions of the Evaluate, Express, and BaseConvert procedures.

{ This collection contains procedures for evaluating a numeral in any base
  from 2 to 36, for expressing a non-negative integer as a numeral in any
  such base, and for converting a numeral in one base into an equivalent
  numeral in another base.

  Programmer: John Stone, Grinnell College.
  Date of this version: July 24, 1996.
}

const
  MinimumBase = 2;
  MaximumBase = 36;

  { frequently used ASCII codes }

  OrdZero = 48 { = Ord ('0') in ASCII };
  OrdCapitalA = 65 { = Ord ('A') in ASCII };
  OrdLowerCaseA = 97 { = Ord ('a') in ASCII };

  { The Evaluate procedure determines the value of a given numeral.  The
    numeral must contain no characters other than digits which are valid in
    the specified base, and the value of the numeral should not exceed
    MaxInt.  The base must be in the range from MinimumBase to
    MaximumBase.

    Although the first parameter of Evaluate is used only for input, I
    have made it a variable-parameter so that when Evaluate is invoked its
    first argument can be a conformant-array parameter (as in procedure
    BaseConvert below). }

  procedure Evaluate (var Numeral: packed array [Low .. High: Integer]
                                                                of Char;
                      var Value: Integer; Base: Integer);
  var
    Index: Integer;
      { counts off the digits of the numeral, from left to right }
    NewDigitValue: Integer;
      { the value of the digit currently being inspected }

    { The ValidDigit function determines whether a given character is a
      valid digit in a given base of numeration.  For bases larger than
      ten, letters of the alphabet are used, and they may be either capital
      or lower-case letters, with the same values (A = a = ten, B = b =
      eleven, and so on) in either case. }

    function ValidDigit (Ch: Char; Base: Integer): Boolean;
    begin
      if Base <= 10 then
        ValidDigit := ('0' <= Ch) and (Ch < Chr (OrdZero + Base))
      else
        ValidDigit := (('0' <= Ch) and (Ch <= '9')) or
             (('A' <= Ch) and (Ch < Chr (OrdCapitalA + Base - 10))) or
             (('a' <= Ch) and (Ch < Chr (OrdLowerCaseA + Base - 10)))
    end;

    { The DigitValue function recovers the numerical value of a given
      digit (or of a letter being used as a digit). }
   
    function DigitValue (Ch: Char): Integer;
    begin
      if ('0' <= Ch) and (Ch <= '9') then
        DigitValue := Ord (Ch) - OrdZero
      else if ('A' <= Ch) and (Ch <= 'Z') then
        DigitValue := Ord (Ch) - OrdCapitalA + 10
      else if ('a' <= Ch) and (Ch <= 'z') then
        DigitValue := Ord (Ch) - OrdLowerCaseA + 10
    end;

    { The InBounds function determines, cautiously, whether the integer
      that would be obtained by multiplying Value and Base and adding
      NewDigitValue to the result is within HP Pascal's Integer data type
      -- that is, whether it would be less than or equal to MaxInt -- 
      returning True if the proposed operation is safe and False if it
      would result in an integer overflow. }

    function InBounds (Value: Integer; NewDigitValue: Integer;
                       Base: Integer): Boolean;
    var
      AllButLast: Integer;
    begin
      AllButLast := MaxInt div Base;
      if Value < AllButLast then
        InBounds := True
      else if Value = AllButLast then
        InBounds := (NewDigitValue <= MaxInt mod Base)
      else
        InBounds := False
    end;

  begin { procedure Evaluate }
    { Assert ((MinimumBase <= Base) and (Base <= MaximumBase)); }
    { Assert (Low <= High); }
    Value := 0;
    for Index := Low to High do begin
      { Assert (ValidDigit (Numeral[Index], Base)); }
      NewDigitValue := DigitValue (Numeral[Index]);
      { Assert (InBounds (Value, NewDigitValue, Base)); }
      Value := Value * Base + NewDigitValue
    end
  end;

  { The Express procedure constructs a numeral, in a specified base of
    numeration, that denotes a given integer value.  The value must be
    non-negative, and the caller must supply a string of sufficient
    maximum length to hold all of the digits of the numeral constructed.
    The base must be in the range from MinimumBase to MaximumBase. }

  procedure Express (Value: Integer;
                     var Numeral: packed array [Low .. High: Integer] of Char;
                     Base: Integer);
  var
    Length: Integer;
      { the number of characters in the completed numeral }
    Index: Integer;
      { counts off positions after the numeral as they are filled with null
        characters }

    { The DigitFor function finds and returns a character that can be used
      as a digit denoting a given integer.  It presupposes that the given
      integer is non-negative and less than the current base of
      numeration. } 

    function DigitFor (N: Integer): Char;
    begin
      if N < 10 then
        DigitFor := Chr (OrdZero + N)
      else
        DigitFor := Chr (OrdCapitalA + N - 10)
    end;

    { Given a one-digit number, the Helper procedure will first ensure that
      there is enough space in the numeral string to write all of the
      digits of the entire value to be expressed, and then place the digit
      that denotes the one-digit number in the first position of that
      string.  Given a number of more than one digit, the helper procedure
      will call itself recursively to place all but the last digit of that
      number in the numeral string, then add the last digit in its correct
      position. }

    procedure Helper (Value: Integer; DigitsSoFar: Integer;
                      var Position: Integer);
    begin
      if Value < Base then begin
        Position := Low;
        { Assert (Position <= High); }
        Numeral[Position] := DigitFor (Value)
      end
      else begin
        Helper (Value div Base, DigitsSoFar + 1, Position);
        Position := Position + 1;
        { Assert (Position <= High); }
        Numeral[Position] := DigitFor (Value mod Base)
      end
    end;

  begin { procedure Express }
    { Assert (0 <= Value); }
    { Assert ((MinimumBase <= Base) and (Base <= MaximumBase)); }
    Helper (Value, 0, Length);
    for Index := Low + Length to High do
      Numeral[Index] := Chr (0)
  end;

  { Given a numeral (NumeralIn) that expresses a value in one specified
    base of numeration (InputBase), the BaseConvert procedure constructs
    a numeral (NumeralOut) that expresses the same value in another
    specified base (OutputBase).  The input numeral must contain no
    characters other than digits which are valid in the specified base, and
    the value it denotes must not exceed MaxInt.  Both bases must be in the
    range from MinimumBase to MaximumBase.

    Although the first parameter of BaseConvert is used only for input, I
    have made it a variable-parameter so that when BaseConvert is invoked
    its first argument can be a conformant-array parameter. }

  procedure BaseConvert (var NumeralIn: packed array
                                    [LowIn .. HighIn: Integer] of Char;
                         InputBase: Integer; OutputBase: Integer;
                         var NumeralOut: packed array
                                    [LowOut .. HighOut: Integer] of Char);
  var
    Value: Integer;
      { the integer value of the input numeral }
  begin
    { Assert ((MinimumBase <= InputBase) and (InputBase <= MaximumBase)); }
    { Assert ((MinimumBase <= OutputBase) and
                                            (OutputBase <= MaximumBase)); }
    Evaluate (NumeralIn, Value, InputBase);
    Express (Value, NumeralOut, OutputBase)
  end;

This document is available on the World Wide Web as

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

created January 9, 1996
last revised July 25, 1996