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 (var numeral: string; var value: integer);
var
index: integer;
begin
value := 0;
for index := 1 to strlen (numeral) do
value := value * 10 + ord (numeral[index]) - ord ('0')
end;
Here I've used the non-standard HP Pascal string type for the
numeral, not specifying the number of characters in the string, so as to be
able to use the same procedure for numerals of any number of digits. HP
Pascal requires that string parameters of unspecified length be passed by
reference rather than by value. Inside the procedure, I've used the
non-standard strlen procedure to determine the number of characters
in the string.
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. HP Pascal provides a mechanism, the
default_parms option, for making this additional parameter
optional. The idea is that the procedure could be invoked either with
three parameters (in which case the third parameter should be the base -- 2
for a binary numeral, 8 for an octal one, and so on) or with two (in which
case the default base of 10 would be used. Here's how it looks in
practice:
procedure evaluate (var numeral: string; var value: integer;
base: integer)
option default_parms (base := 10);
var
index: integer;
begin
value := 0;
for index := 1 to strlen (numeral) do
value := value * base + ord (numeral[index]) - ord ('0')
end;
Of course, if you use this mechanism, your program will not be portable;
the default_parms option is extremely non-standard Pascal.
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 HP Pascal's integer
type. The base_conversion module at the end of this handout
includes 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: string);
var
length: integer;
procedure helper (value: integer; digits_so_far: integer;
var position: integer);
begin
if value < 10 then begin
position := 1;
setstrlen (numeral, digits_so_far + 1);
numeral[position] := chr (ord ('0') + value)
end
else begin
helper (value div base, digits_so_far + 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
digits_so_far 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.
The call to the HP Pascal procedure setstrlen ensures that all
the positions into which digits must be written will be accessible.
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
base_conversion module 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 base_convert (var numeral_in: string; input_base: integer;
output_base: integer; var numeral_out: string);
var
value: integer;
begin
evaluate (numeral_in, value, input_base);
express (value, numeral_out, output_base)
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 base_convert
procedures.
{ This module exports 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: January 9, 1996.
}
{ The evaluate and express procedures defined below can be invoked with
either two arguments or three arguments. The third argument, if present,
specifies the base of numeration. If it is absent, decimal numeration is
assumed. The following compiler directive makes it possible to define
procedures with optional parameters. }
$standard_level 'ext_modcal'$
module base_conversion;
export
const
MINIMUM_BASE = 2;
MAXIMUM_BASE = 36;
{ 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 2 to 36; if it is not
supplied, a default value of 10 is assumed. }
procedure evaluate (var numeral: string; var value: integer;
base: integer)
option default_parms (base := 10);
{ 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 2 to 36; if it is not supplied, a
default value of 10 is assumed. }
procedure express (value: integer; var numeral: string; base: integer)
option default_parms (base := 10);
{ Given a numeral (numeral_in) that expresses a value in one specified
base of numeration (input_base), the base_convert procedure constructs
a numeral (numeral_out) that expresses the same value in another
specified base (output_base). 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 2 to 36. }
procedure base_convert (var numeral_in: string; input_base: integer;
output_base: integer; var numeral_out: string);
implement
import
stderr;
const
{ frequently used ASCII codes }
ORD_ZERO = 48 { = ord ('0') in ASCII };
ORD_CAPITAL_A = 65 { = ord ('A') in ASCII };
ORD_LOWER_CASE_A = 97 { = ord ('a') in ASCII };
{ The following constants are more or less arbitrary integers
signifying various kinds of exceptions that can occur within this
module. }
FIRST_EXCEPTION_CODE = 1;
NULL_NUMERAL_EXCEPTION = 1;
BAD_DIGIT_EXCEPTION = 2;
INTEGER_OVERFLOW_EXCEPTION = 3;
STRING_OVERFLOW_EXCEPTION = 4;
NEGATIVE_EXCEPTION = 5;
BASE_RANGE_EXCEPTION = 6;
EXCEPTION_EXCEPTION = 7;
LAST_EXCEPTION_CODE = EXCEPTION_EXCEPTION;
{ The numeral_exception procedure, which is not exported, is invoked
whenever one of the preconditions for the successful execution of a
procedure is found to be false. It prints out an appropriate
explanation of the exception just before the program is halted. }
procedure numeral_exception (exception_code: integer);
begin
if (exception_code < FIRST_EXCEPTION_CODE) or
(LAST_EXCEPTION_CODE < exception_code) then
exception_code := EXCEPTION_EXCEPTION;
write (stderr, 'Exception #', exception_code : 1,
' in module NUMERALS: ');
case exception_code of
NULL_NUMERAL_EXCEPTION:
writeln (stderr, 'null string presented as numeral in procedure ',
'EVALUATE');
BAD_DIGIT_EXCEPTION:
writeln (stderr, 'non-digit occurring in numeral in procedure ',
'EVALUATE');
INTEGER_OVERFLOW_EXCEPTION:
writeln (stderr, 'value of numeral exceeds MAXINT in procedure ',
'EVALUATE');
STRING_OVERFLOW_EXCEPTION:
writeln (stderr, 'length of numeral exceeds maximum length of ',
'string in procedure EXPRESS');
NEGATIVE_EXCEPTION:
writeln (stderr, 'negative number as argument to procedure ',
'EXPRESS');
BASE_RANGE_EXCEPTION:
writeln (stderr, 'base out of range (', MINIMUM_BASE : 1, ' .. ',
MAXIMUM_BASE : 1, ') in procedure EVALUATE, procedure ',
'EXPRESS, or procedure BASE_CONVERT');
EXCEPTION_EXCEPTION:
writeln (stderr, 'argument out of range in procedure ',
'NUMERAL_EXCEPTION.');
end
end;
procedure evaluate (var numeral: string; var value: integer;
base: integer)
option default_parms (base := 10);
var
index: integer;
{ counts off the digits of the numeral, from left to right }
new_digit_value: integer;
{ the value of the digit currently being inspected }
{ The valid_digit 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 valid_digit (ch: char; base: integer): Boolean;
begin
if base <= 10 then
valid_digit := ('0' <= ch) and (ch < chr (ORD_ZERO + base))
else
valid_digit := (('0' <= ch) and (ch <= '9')) or
(('A' <= ch) and (ch < chr (ORD_CAPITAL_A + base - 10))) or
(('a' <= ch) and (ch < chr (ORD_LOWER_CASE_A + base - 10)))
end;
{ The digit_value function recovers the numerical value of a given
digit (or of a letter being used as a digit). }
function digit_value (ch: char): integer;
begin
if ('0' <= ch) and (ch <= '9') then
digit_value := ord (ch) - ORD_ZERO
else if ('A' <= ch) and (ch <= 'Z') then
digit_value := ord (ch) - ORD_CAPITAL_A + 10
else if ('a' <= ch) and (ch <= 'z') then
digit_value := ord (ch) - ORD_LOWER_CASE_A + 10
end;
{ The in_bounds function determines, cautiously, whether the integer
that would be obtained by multiplying value and base and adding
new_digit_value 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 in_bounds (value: integer; new_digit_value: integer;
base: integer): Boolean;
var
all_but_last: integer;
begin
all_but_last := MAXINT div base;
if value < all_but_last then
in_bounds := TRUE
else if value = all_but_last then
in_bounds := (new_digit_value <= MAXINT mod base)
else
in_bounds := FALSE
end;
begin { procedure evaluate }
assert ((MINIMUM_BASE <= base) and (base <= MAXIMUM_BASE),
BASE_RANGE_EXCEPTION, numeral_exception);
assert (0 < strlen (numeral), NULL_NUMERAL_EXCEPTION,
numeral_exception);
value := 0;
for index := 1 to strlen (numeral) do begin
assert (valid_digit (numeral[index], base), BAD_DIGIT_EXCEPTION,
numeral_exception);
new_digit_value := digit_value (numeral[index]);
assert (in_bounds (value, new_digit_value, base),
INTEGER_OVERFLOW_EXCEPTION, numeral_exception);
value := value * base + new_digit_value
end
end;
procedure express (value: integer; var numeral: string; base: integer)
option default_parms (base := 10);
var
length: integer;
{ the number of characters in the completed numeral }
max_length: integer;
{ the number of character positions available to hold the completed
numeral }
{ The digit_for 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 digit_for (n: integer): char;
begin
if n < 10 then
digit_for := chr (ORD_ZERO + n)
else
digit_for := chr (ORD_CAPITAL_A + 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; digits_so_far: integer;
var position: integer);
begin
if value < base then begin
position := 1;
assert (position <= max_length, STRING_OVERFLOW_EXCEPTION,
numeral_exception);
setstrlen (numeral, digits_so_far + 1);
numeral[position] := digit_for (value)
end
else begin
helper (value div base, digits_so_far + 1, position);
position := position + 1;
assert (position <= max_length, STRING_OVERFLOW_EXCEPTION,
numeral_exception);
numeral[position] := digit_for (value mod base)
end
end;
begin { procedure express }
assert (0 <= value, NEGATIVE_EXCEPTION, numeral_exception);
assert ((MINIMUM_BASE <= base) and (base <= MAXIMUM_BASE),
BASE_RANGE_EXCEPTION, numeral_exception);
max_length := strmax (numeral);
helper (value, 0, length)
end;
procedure base_convert (var numeral_in: string; input_base: integer;
output_base: integer; var numeral_out: string);
var
value: integer;
{ the integer value of the input numeral }
begin
assert ((MINIMUM_BASE <= input_base) and (input_base <= MAXIMUM_BASE),
BASE_RANGE_EXCEPTION, numeral_exception);
assert ((MINIMUM_BASE <= output_base) and
(output_base <= MAXIMUM_BASE),
BASE_RANGE_EXCEPTION, numeral_exception);
evaluate (numeral_in, value, input_base);
express (value, numeral_out, output_base)
end;
end.
This document is available on the World Wide Web as
http://www.math.grin.edu/~stone/courses/fundamentals/base-conversion.html