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.
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.
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.
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.
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