The phrase "unbounded precision" means that the program providing the calculation itself places no limitation on the number of digits in a value. There are limitations to the storage capacity of any computing system; but they are generally very large relative to values that are likely to be used, and the program simply ignores that bound.
The program Unbound presented in this section is a simulation of a calculator used in infix mode: a value, an operator, and then a value are entered and the result display is prompted by the entry of a character " = '' or one that represents another operator. The functions provided are addition ("+"), subtraction ("-"), multiplication ("*"), division ("/"), display ("="), clear the accumulator ("C''), and quit ("Q"). The inclusion of M + (add to memory), M - (subtract from memory), MRC (recover from memory), and MCL (memory clear) are left as a challenge.
The program Unbound is developed in this section, rather than simply described or listed, in order to document the series of design decisions implemented by the program. The logic of Unbound is clarified by developing it in pseudocode and then translating that logic into Pascal.
A crucial feature of a calculator is that it is fairly robust: mistaken entries do little harm, and that feature should be retained in a simulating program. The possibilities for ambiguous entries are much increased with the use of a terminal keyboard in place of the more restricted calculator keypad. In that spirit, extraneous characters are ignored. The accepted characters are digits 0 -9 and operators ''+'', "-", ''*'', ''/'', '' = '', ''C'', ''Q''. All others are ignored. For example, A17B3 is accepted as 173. For the sake of robustness, division by 0 simply results in 0.
The basic response of the program is designed to deal with entry sequences such as: 24 + 9 =, which results in a display of 33 and the retention of that value in an accumulator (represented by the variable AC). A string of digits that designates a number is terminated by an operator, and so it is (value,operator)-pairs that are accepted by the program. Although an operator terminates the entry of a number, it is the previous operator that usually determines the action. (Operators such as ''C" and ''Q'' are exceptions.) In more detail, if the entering value is X and the result of arithmetic is held in AC, the action of this sequence can be traced as shown in Table D.1.
The operation of most calculators is more complex than this example seems to imply. What, for example, is the result of the following entry sequences?
The results generated by the program developed here are -15, -15, 24, 48, and 72, respectively. The last of these is not standard, but it is convenient to have each new operator invoke the response of the previous one. If no new X is entered, the AC value is used as X.
A procedure Preface is used to initialize variables and provide instructions to the user; Clear is used to set registers such as AC to zero; and Display writes out the value in the accumulator AC. These utility routines are supplemented by arithmetic procedures that act on AC.
The procedure ValueIn that extracts the tokens X and NextOp also returns a Boolean flag NoX to indicate the presence of a numeric value between operators. The command-shell executes the previous operator, Op, in response to the next operator, NextOp. If no X-value is entered withNextOp, then the current value of AC is copied into X. Operators "Q'' and ''C'' require immediate response. When "Q'' is NextOp, it causes immediate termination of the program. When NextOp is ''C'' both AC and X are cleared, and Op is set to "C'' also. When Op is either "C'' or ''='', and an X-value is entered with NextOp, the X-value becomes the new AC value.
The command-shell program that results is:
The next level of detail is dependent on the implementation of values, and that implementation must allow assignments X
The Sign field is Boolean, with TRUE corresponding to positive values--making tests involving signs more convenient.
A header such as AC or X can then be initialized by:
Setting an initialized register back to zero can be even simpler:
This procedure, however, does not recover the (possibly many) digit nodes that were in the register R. A more practical version of Clear is:
The speed of arithmetic operations is directly dependent on the number of digits involved, and so removal of duplicate leading zeroes is an essential part of housekeeping:
Note: The pair of tests at the top of this loop require a fair amount of modification when implemented in Pascal because the condition:
is risky. A more direct translation results from the use of GOTO to implement exit.
On the principle of "never trust the user," compaction is included as the last action in ValueIn.
The input-token generator ValueIn is:
The fundamental operation of arithmetic is addition, but it becomes subtraction when the signs of the operands differ. When done by hand, the value of smaller magnitude is subtracted from the other. One simple approach to addition is based on two steps:
1. Compare AC and X and switch them if necessary to insure that
2. If the signs agree, then add magnitudes with procedure Carry; otherwise, subtract the magnitudes with procedure Borrow.
The resulting logic is:
Note: The returns are replaced by a nest of IF-statements in Pascal. Add, Subtract, Multiply, and Divide all may switch X and AC to avoid copying a long list of digits, and so X is a VAR parameter in the Pascal implementation.
The last carry operation can create a new digit (as in 997 + 18), which is signaled by a carry after the last X-digit has been added to AC. The last borrow can propagate to the left (as in 10003 - 4), but that can be handled by continuing to subtract the leading zero node, X
With Add complete, subtraction becomes very simple:
Multiplication can be done by repeated addition in two ways: 47
Several values are involved in multiplication: the original operands X and AC, the shifting values Y (such as 47, 470, . . . ), the product of a digit of X with a Y, say M, and the running sum of these products. AC supplies the original Y and then may contain the running sum. The product sign must be determined but not affect the use of Add to multiply magnitudes, and a value with a minimal number of digits should be used to control the multiplication loop. This housekeeping is done by:
A routine is needed to shift successive products one digit to the left. One approach is to shift Y:
Multiplication then can be done with:
Division by repeated subtraction is as slow as multiplication by repeated addition, but the alternative algorithm used by fifth graders is much more complex than repeated subtraction. For contrast with multiplication, a version of repeated subtraction is applied here: the divisor is added to itself until it is larger than the dividend. Each such addition is paired with an increment of the result.
Auxiliary registers are used as they are for multiplication and destroyed on exit from Divide. The initialization is:
The resulting divide logic is:
A listing of a Pascal program that results from this design follows:
D.1 The Entry Sequence
Table D.1
Entries X NewOp Op AC
--------------------------
C 0
24+ 24 + C 24
9= 9 = + 33
- 24 + 9 =
- 24 + 9 +
24 + +
24 + + =
24 + + + =
D.2 Utility Routines
program UnBound
Preface
Done
FALSEOp
'C'repeat
ValueIn(X,NoX,NextOp)
if NextOp = 'Q'
then Done
TRUEelse if NextOp = 'C'
then Clear(AC)
Clear(X)
Op
'C'endif
if NoX then X
AC endifcase of Op
'=', 'C': Swap {AC with X'+' :Add(AC,X)
{'-', '*', '/' are similar to '+'}endcase
endif
Op
NextOpDisplay
until Done
end {UnBound
AC and AC
X for unbounded strings of digits. Values need to carry a sign, have leading zeroes suppressed for the sake of efficiency and display, be compared in magnitude (left-to-right for corresponding digits), and be used right-to-left in arithmetic operations. The chosen implementation for AC and X is a pointer to a header of a doubly linked list. Such a list plays the role of a register--a storage location directly associated with computation. For reasons that only become apparent when operations are examined in detail, it is convenient to carry one leading zero and link it to itself. When AC
743, the resulting register structure is shown in Figure D.1.
Figure D.1
procedure NewRegister(R)
NEW(d)
d
.Left
dd
.Value
0d
.Right
NILNEW(R)
R
.First
dR
.Last
dR
.Sign
TRUER
.Count
0end {NewRegisterprocedure Clear(R)
R
.Last
R
.FirstR
.Count
0R
.Sign
TRUEend {Clearprocedure Clear(R)
p
R
.Lastq
p
.Leftwhile p
R
.First doDISPOSE(p)
p
qq
p
.Leftendwhile
p
.Right
NILR
.Last
pR
.Count
0R
.Sign
TRUEend {Clearprocedure Compact(R)
p
R
.Firstq
p
.Rightwhile q
NIL doif q
.Value
0then exit
else t
q
.RightDISPOSE(q)
R
.Count
R
.Count - 1P
.Right
tq
tif t
NILthen t
.Left
p endifendif
endwhile
if R
.Count = 0 then R
.Last
R
.First endifend {Compact(q
NIL) AND (q
.Value
0)procedure ValueIn(X,NoX,NextOp)
NoX
TRUEClear(X)
Read(ch)
while NOT (ch IN OperatorSet) do
if ch IN [0..9]
then NoX
FALSENEW(d)
d
.Right
NILd
.Value
ORD(ch) - ORD('0')d
.Left
X
.LastX
.Last
.Right
dX
.Last
dX
.Count
X
.Count + 1endif
Read(ch)
endwhile
NextOp
chCompact(X)
end {ValueInD.3 Arithmetic Operations
AC
X
.procedure Add(X)
R
LargerOf(AC,X)if R = X
then X
ACAC
Rendif
if X
.Count = 0 then return endifif AC
.Sign = X
.Signthen Carry(AC,X)
else Borrow(AC,X)
endif
end {Addfunction LargerOf(AC,X) {O(n) Assumes compacted operandsif AC
.Count > X
.Count then return AC endifif X
.Count > AC
.Count then return X endifk
AC
.CountpAC
AC
.First
.RightpX
X
.First
.Rightwhile k > 0 do
if pAC
.Value > pX
.Value then return AC endifif pAC
.Value < pX
.Value then return X endifpAC
pAC
.RightpX
pX
.Rightk
k - 1endwhile
return AC {X = ACend {LargerOf
.First. With these problems recognized:procedure Carry(X)
pAC
AC
.LastpX
X
.LastOver
0for k = 1 to AC
.Count doSum
pAC
.Value + pX
.Value + Overif Sum > 9
then Over
1pAC
.Value
Sum - 10else Over
0pAC
.Value
Sumendif
pAC
pAC
.LeftpX
pX
.Leftnext k
if Over > 0
then NEW(d)
d
.Right
pAC
.Rightd
.Left
pACpAC
.Right
.Left
dpAC
.Right
dd
.Value
OverAC
.Count
AC
.Count + 1endif
end {Carryprocedure Borrow
pAC
AC
.LastpX
X
.LastLoan
0for k = 1 to AC
.Count doDifference
pAC
.Value - pX
.Value - Loanif Difference < 0
then Loan
1pAC
.Value
Difference + 10else Loan
0pAC
.Value
Differenceendif
pAC
pAC
.LeftpX
pX
.Leftnext k
Compact(AC)
end {Borrowprocedure Subtract(X)
X
.Sign
NOT X
.SignAdd(X)
end {Subtract
31 = (47 + 47 + . . . + 47, 31 times) and 47
31 = (47 + 3
470). The latter approach is used here. It will square 2 raised to the 96th power in a few seconds on an inexpensive microcomputer (after compilation by a $30 Pascal compiler). This operation would take years by repeated addition.procedure SetUp
Y
ACNewRegister(AC)
New Register(M)
R
LargerOf(Y,X)if R = X
then X
YY
Rendif
NewSign
(Y
.Sign = X
.Sign)X
.Sign
TRUEY
.Sign
TRUEClear(AC)
end {SetUpprocedure ShiftLeft(Y)
NEW(d)
d
.Value
Od
.Right
NILd
.Left
Y
.LastY
.Count
Y
.Count + 1Y
.Last
.Right
dY
.Last
dend {ShiftLeftprocedure Multiply(X)
SetUp
if X
.Count = 0 then return endifpX
X
.Lastfor j = 1 to X
.Count doOver
0MakeCopy(M,Y)
pM
M
.LastpX
X
.Lastv
pX
.Valuefor k = 1 to M
.Count doProduct
pM
.Value * v + OverpM
.Value
Product MOD 10Over
Product DIV 10pM
pM
.Leftnext k
if Over > 0
then NEW(d)
d
.Right
pM
.Rightd
.Left
pMpM
.Right
.Left
dpM
.Right
dM
.Count
M
.Count + 1d
.Value
Overendif
Add(AC,M)
ShiftLeft(Y)
pX
pX
.Leftnext j
AC
.Sign
NewSignClear(Y)
Clear(M)
end {Multiplyprocedure Prolog
NewSign
(AC
.Sign = X
.Sign)AC
.Sign
TRUEX
.Sign
TRUED
ACNewRegister(AC)
NewRegister(R)
NewRegister(S)
end {Prologprocedure Divide(X)
if X
.Count = 0then Clear(AC)
return
endif
Prolog
MakeCopy(S,X)
MakeCopy(AC,One)
R
LargerOf(D,S)while R = D do
Carry(AC,One)
Carry(S,X)
R
LargerOf(D,S)endwhile
Borrow(AC,One)
AC
.Sign
NewSignClear(D)
Clear(S)
end {DividePROGRAM UnBound (InPut,OutPut);
TYPE Digit =
DigitNode;Register =
RegisterNode;DigitNode = RECORD
Value : 0 . . 9;
Left : Digit;
Right : Digit
END;
RegisterNode = RECORD
Sign : BOOLEAN;
Count : INTEGER;
First : Digit;
Last : Digit
END;
VAR Op ,NextOP : CHAR;
NoX,Done : BOOLEAN;
One,X,AC : Register;
OperatorSet : SET OF CHAR;
Numeric : SET OF CHAR;
{ }PROCEDURE Display (AC : Register);
VAR pAC : Digit;
k : INTEGER;
BEGIN
Write1n;
pAC := AC
.First;IF NOT (AC
. Sign) THEN Write('-');FOR k := 1 TO (AC
. Count + 1) DO BEGINWrite(pAC
.Value);pAC := pAC
. RightEND;
Write1n
END {Display};{ }PROCEDURE NewRegister(VAR R : Register);
VAR d : Digit;
BEGIN
NEW(d);
d
.Left := d;d
.Value := 0;d
.Right := NIL;NEW(R);
R
.First := d;R
.Last := d;R
.Sign := TRUE;R
.Count := 0END {NewRegister};{ }PROCEDURE Clear (VAR R : Register);
VAR p , q : Digit;
BEGIN
p := R
.Last;q := P
.Left;WHILE (p <> R
.First) DO BEGINDISPOSE(p);
p := q;
q := p
.LeftEND;
R
.Last := p;R
.Count := 0;P
.Right := NIL;R
.Sign := TRUEEND {Clear};{ }PROCEDURE MakeOne(VAR One : Register);
VAR d : Digit;
BEGIN
NewRegister(One);
NEW(d);
d
.Value := 1;d
.Left := One
.First;d
.Right := NIL;One
.First
.Right := d;One
.Last := d;One
.Count := 1END {MakeOne};{ }PROCEDURE Preface;
BEGIN
Write1n('This program simulates a calculator that');Write1n('allows unbounded integers. Enter values');Write1n('followed by operators. The accepted operators');Write1n('are: +,-,*,/,=,C (for clear),Q (for quit).');NewRegister(AC);
NewRegister(X);
MakeOne(One);
Numeric := ['0'..'9'];
OperatorSet := ['+','-','*','/','=','C','Q']
END {Preface};{ }PROCEDURE Compact(VAR R : Register);
VAR p, q, t : Digit;
BEGIN
p := R
.First;q := p
.Right;IF (q <> NIL) THEN IF (q
.Value <> 0) THEN q:= NIL;WHILE (q <> NIL) DO BEGIN
t := q
.Right;DISPOSE(q);
R
.Count := R
.Count - 1;P
.Right := t;q := t;
IF (t <> NIL)
THEN BEGIN
t
.Left := p;IF (t
.Value <> 0) THEN q := NILEND
END;
IF (R
.Count = 0) THEN R
.Last := R
.FirstEND {Compact};{ }PROCEDURE ValueIn(VAR X : Register; VAR NoX : BOOLEAN;
VAR NextOP : CHAR);
VAR ch : CHAR;
d : Digit;
BEGIN
NoX := TRUE;
Clear(X);
Read(ch);
WHILE NOT (ch IN OperatorSet)
DO BEGIN
IF (ch IN Numeric)
THEN BEGIN
NoX := FALSE;
NEW(d);
d
.Right := NIL;d
.Value := ORD(ch) - ORD('0');d
.Left := X
.Last;X
.Last
.Right := d;X
.Last := d;X
.Count := X
.Count + 1END;
Read(ch)
END;
NextOp := ch;
Compact(X)
END {ValueIn};{ }FUNCTION LargerOf(AC,X : Register) : Register;
VAR K : INTEGER;
pAC,pX : Digit;
BEGIN
LargerOf := AC;
IF (AC
.Count <= X
.Count) THENIF (AC
.Count < X
.Count)THEN LargerOf := X
ELSE BEGIN
k := AC
.Count;pAC := AC
.First
.Right;pX := X
.First
.Right;WHILE (k > 0) DO
IF (pAC
.Value < = pX
.Value)THEN IF (pAC
.Value < pX
.Value)THEN BEGIN
LargerOf := X;
K := O
END
ELSE BEGIN
pAC := pAC
.Right;pX := pX
.Right;k := k - 1
END
ELSE k := O
END;
END {LargerOf};{ }PROCEDURE MakeCopy(VAR M : Register; Y : Register);
VAR d,pY : Digit;
k : INTEGER;
BEGIN
Clear(M);
IF (Y
.Count > 0) THEN BEGINpY := Y
.First
.Right;FOR k := 1 TO Y
.Count DO BEGINNEW(d);
d
.Right := NIL;d
.Value := PY
.Value;pY := pY
.Right;d
.Left := M
.Last;M
.Last
.Right := d;M
.Last := dEND;
M
.Count := Y
.Count;END
END {MakeCopy};{ }PROCEDURE Carry(VAR AC : Register; X : Register);
VAR pAC, pX, d : Digit;
k,Sum, Over : INTEGER;
BEGIN
pAC := AC
.Last;pX := X
.Last;Over := 0;
FOR K := 1 TO AC
.Count DO BEGINSum := pAC
.Value + pX
.Value + Over;IF (Sum > 9)
THEN BEGIN
Over := 1;
pAC
.Value := Sum - 10END
ELSE BEGIN
Over := 0;
pAC
.Value := SumEND;
pAC := pAC
.Left;pX := pX
.LeftEND;
IF (Over > 0)
THEN BEGIN
NEW(d);
d
.Right := pAC
.Right;d
.Left := pAC;pAC
.Right
.Left := d;pAC
.Right := d;d
.Value := Over;AC
.Count := AC
.Count + 1END
END {Carry};{ }PROCEDURE Borrow(VAR AC: Register; X: Register);
VAR pAC,pX: Digit;
k,Loan,Difference: INTEGER;
BEGIN
pAC := AC
.Last;pX := X
.Last;Loan := 0;
FOR k := 1 TO AC
.Count DO BEGINDifference := pAC
.Value - pX
.Value - Loan;IF (Difference < 0)
THEN BEGIN
Loan := 1;
pAC
.Value := Difference + 10END
ELSE BEGIN
Loan := 0;
pAC
.Value := DifferenceEND;
pAC := pAC
.Left;pX := pX
.LeftEND;
Compact(AC)
END {Borrow};{ }PROCEDURE Add(VAR AC: Register; VAR X : Register);
VAR R: Register;
BEGIN {X is VAR as AC may swap with it.}R := LargerOf(AC,X);
IF (R = X)
THEN BEGIN
X := AC;
AC := R
END;
IF (X
.Count <>0)THEN IF (AC
.Sign = X
.Sign)THEN Carry(AC,X)
ELSE Borrow(AC,X)
END {Add};{ }PROCEDURE Subtract(VAR AC: Register; VAR X: Register);
BEGIN
X
.Sign := NOT X
.Sign;Add(AC,X)
END {Subtract};{ }PROCEDURE Multiply (VAR AC: Register; VAR X: Register);
VAR pM,pX,d: Digit;
M,Y : Register;
v,k,j,Over,Product: INTEGER;
NewSign: BOOLEAN;
{ }PROCEDURE SetUp;
VAR R: Register;
BEGIN
Y := AC;
NewRegister(AC);
NewRegister(M);
R := LargerOf( Y,X);
IF (R = X)
THEN BEGIN
X := Y;
Y := R
END;
NewSign := (Y
.Sign = X
.Sign);X
.Sign := TRUE;Y
.Sign := TRUE;Clear(AC)
END {SetUp};{ }PROCEDURE ShiftLeft(VAR Y: Register);
VAR d: Digit;
BEGIN
NEW (d);
d
.Value := 0;d
.Right := NIL;d
.Left := Y
.Last;Y
.Count := Y
.Count + 1;Y
.Last
.Right := d;Y
.Last := dEND {ShiftLeft};{ }BEGIN {Multiply}SetUp;
IF (X
.Count <> 0)THEN BEGIN
pX := X
.Last;FOR j := 1 TO X
.Count DO BEGINOver := 0;
MakeCopy(M,Y);
pM := M
.Last;v := pX
.Value;FOR k := 1 TO M
.Count DO BEGINProduct:=pM
.Value* v + Over;pM
.Value := Product MOD 10;Over := Product DIV 10;
pM := pM
.LeftEND;
IF (Over > 0)
THEN BEGIN
NEW (d);
d
.Right := pM
.Right;d
.Left := pM;pM
.Right
.Left := d;pM
.Right := d;M
.Count := M
.Count + 1;d
.Value := OverEND;
Add(AC,M);
ShiftLeft(Y);
pX := pX
.LeftEND;
AC
.Sign := NewSign;Clear(Y);
Clear(M)
END
END {Multiply};{ }PROCEDURE Divide(VAR AC: Register; VAR X: Register);
VAR NewSign: BOOLEAN;
D,R,S : Register;
{ }PROCEDURE Prolog;
BEGIN
NewSign := (AC
.Sign = X
.Sign);AC
.Sign := TRUE;X
.Sign := TRUE;D := AC;
NewRegister(AC);
NewRegister(R);
NewRegister(S)
END {Prolog};{ }BEGIN {Divide}IF (X
.Count = 0)THEN Clear(AC)
ELSE BEGIN
Prolog;
MakeCopy(S,X);
MakeCopy(AC,One);
R := LargerOf(D,S);
WHILE (R = D) DO BEGIN
Carry(AC,One);
Carry(S,X);
R := LargerOf(D,S)
END;
Borrow(AC,One);
AC
.Sign := NewSign;Clear(D); Clear(S)
END
END {Divide};{ }PROCEDURE Swap;
VAR R: Register;
BEGIN
R := AC;
AC := X;
X := R
END {Swap};{****}
BEGIN {UnBound}Preface;
Done := FALSE;
Op := 'C';
REPEAT
ValueIn(X,NoX,NextOp);
IF (NextOp = 'Q')
THEN Done = TRUE
ELSE BEGIN
IF (NextOp = 'C')
THEN BEGIN
Clear(X); Clear(AC);
Op :='C'
END;
IF NoX THEN MakeCopy(X,AC);
CASE Op OF
'C' : Swap; {AC with X}'=' : Swap;
'+' : Add(AC,X);
'-' : Subtract(AC,X);
'*' : Multiply(AC,X);
'/' : Divide(AC,X)
END
END;
Op := NextOp;
Display(AC)
UNTIL Done
END {UnBound}.