One of the problems we have when manipulating the type REAL is that numbers stored in this form are subject to being rounded off. In working with dollar amounts, rounding off errors are simply not acceptable, so many computer languages or operating environments provide some means for expressing dollar figures as sequences of digits.

*Numbers that are stored as sequences of exact digits are said to be of type Decimal.*

If this is done, and we have *472* *+* *231*, a special procedure is required as in Section 7.6 to add the digits one column at a time starting from the right-hand-side and get the sequence *703*.

Subtraction and multiplication present their own challenges, as does printing such quantities out, for one may store them initially as strings of digits, but when it comes to printing them, one would probably want to enter those digits into a format defined in another string and output "$7.03". (In this case, the format string used is "$#!##" where each *#* is a digit and the *!* indicates the location of the decimal point in the result. See the examples later for details.

*A model string used to specify a format for Decimal data I/O is called a format string or a picture or a mask. The purpose of a picture is to indicate where the punctuation marks are in the output string.*

Various notations have different provisions for handling such types of data and for performing these operations. In some versions of Pascal, the predecessor of Modula-2, there is a built-in facility to define long integers of any specified number of digits, merely by stating in brackets after the TYPE definition the number of digits for that Integer type. COBOL, Fortran, and some BASICs have the ability to reformat such numbers into strings which can be written out as dollar amounts, social security numbers, or in any other desired fashion.

As may be guessed because of the treatment of the problem of multiplying such quantities in Chapter 7, no such data type or ability is built-in to Modula-2. Modules to implement such types are provided with some versions of Modula-2 and if such a module is available to the reader, there are some straightforward exercises on its use at the end of this chapter. If it is not, the challenge is to write it, using some of the methods of Chapter 7 and later chapters. Only a portion of that work will be shown here; the rest is left as exercises for the reader.

**NOTE:** Unlike the situation with complex numbers, there is *no* provision in ISO standard Modula-2 for such a data type. The user who needs such a facility is at the mercy of her own ability to write it or the vendor's to provide it.

The module below is one possibility for a definition of such an ADT. It can be modified for Canadian, American, or European style numeric output by changing the constants *decPoint* and *separator* and by supplying a different currency symbol in the picture string.

DEFINITIONMODULEDecimals; (* by R. Sutcliffe last modified 1996 11 05 *)CONSTMaxDigits = 19; decPoint = '.'; separator = ' ';TYPEDigit = [0..9]; DecRange = [0 .. MaxDigits - 1]; DecState = (allOK, NegOvfl, PosOvfl, Invalid); DecHandler =PROCEDURE(DecState); CompareResults = (less, equal, greater); Decimal =RECORDstate : DecState; isNeg :BOOLEAN; number :ARRAYDecRangeOFDigit;END;CONSTzero = Decimal {allOK,FALSE, {0BYMaxDigits}};PROCEDURESetHandler (handler: DecHandler);PROCEDUREAbs (dec : Decimal): Decimal;PROCEDUREAdd (dec1, dec2: Decimal): Decimal;PROCEDURESub (dec1, dec2: Decimal): Decimal;PROCEDUREMul (dec1, dec2: Decimal): Decimal;PROCEDUREDiv (dec1, dec2: Decimal): Decimal;PROCEDURERemainder (): Decimal;PROCEDURECompare (dec1, dec2: Decimal): CompareResults;PROCEDURENeg (dec: Decimal): Decimal;PROCEDUREStatus (dec: Decimal): DecState;ENDDecimals.

For reasons similar to those given in the initial discussions in the last section, the numeric type is here implemented transparently. An opaque implementation would require regular procedures and variable parameters to return results of numeric operations.

The module *Decimals* exports the type *Decimal*, which can be thought of as an 19-digit long integer. Provision is made to store an error state and a sign for each such entity. Of course, *Decimal* should be treated as an opaque type, as though the details were not available in the definition module. *Decimal*s also exports an apparatus for error handling that consists of a type *DecState* that defines the error values, a *Status* enquiry procedure to discover the state of any individual item, and a *Handler *type to define the type of an error handler procedure that a client can attach using *SetHandler*. This handler defaults to a procedure that does nothing at all, but a program can define a procedure taking the *DecState* parameter, and set it as desired. The procedure *Remainder* is intended to fetch the stored remainder of the last division performed.

**WARNING:** The implementation shown below is minimal and incomplete. In particular there are a minimum of comments. Completing it is the subject of some of the exercises at the end of the chapter.

IMPLEMENTATIONMODULEDecimals; (* by R. Sutcliffe last modified 1996 11 05 *)VARremainder : Decimal; theHandler : DecHandler;PROCEDUREDefaultHandler (theError : DecState); (* does nothing *)ENDDefaultHandler; (* exported procs *)PROCEDURESetHandler (handler: DecHandler);BEGINtheHandler := handler;ENDSetHandler;PROCEDUREAbs (dec : Decimal): Decimal;BEGINdec.isNeg :=FALSE;RETURNdec;ENDAbs;PROCEDUREAdd (dec1, dec2: Decimal): Decimal;VARcount, temp, carry :CARDINAL; result : Decimal;BEGINresult := zero; carry := 0; (* if both pos or both neg, just add the digits up *)IF((dec1.isNeg)AND(dec2.isNeg))ORNOT((dec1.isNeg)OR(dec2.isNeg))THENFORcount := 0TOMaxDigits - 1DOtemp := carry + dec1.number[count] + dec2.number[count]; result.number[count] := tempMOD10; carry := tempDIV10;END; (* attach the common sign *) result.isNeg := dec1.isNeg;IFcarry # 0THENIFresult.isNegTHENresult.state := PosOvflELSEresult.state := NegOvflEND;END;ELSE(* one is neg, the other pos so find difference *)IFCompare (Abs (dec1), Abs (dec2)) = greaterTHENFORcount := 0TOMaxDigits - 1DODEC(dec1.number[count], carry);IFdec1.number[count] >= dec2.number[count]THENresult.number[count] := dec1.number[count] - dec2.number[count]; carry := 0;ELSEresult.number[count] := 10 + dec1.number[count] - dec2.number[count]; carry := 1;END;END; (* attach sign of larger in absolute value *) result.isNeg := dec1.isNeg;ELSIFCompare (Abs (dec1), Abs (dec2)) = lessTHENFORcount := 0TOMaxDigits - 1DODEC(dec1.number[count], carry);IFdec2.number[count] >= dec1.number[count]THENresult.number[count] := dec2.number[count] - dec1.number[count]; carry := 0;ELSEresult.number[count] := 10 + dec2.number[count] - dec1.number[count]; carry := 1;END;END; (* attach sign of larger in absolute value *) result.isNeg := dec2.isNeg;END;END; (* always call error handler before concluding *) theHandler (result.state);RETURNresult;ENDAdd;PROCEDURESub (dec1, dec2: Decimal): Decimal;BEGINRETURNAdd (dec1, Neg (dec2));ENDSub;PROCEDUREMul (dec1, dec2: Decimal): Decimal; (* exercise *)ENDMul;PROCEDUREDiv (dec1, dec2: Decimal): Decimal; (* exercise *)ENDDiv;PROCEDURERemainder (): Decimal;BEGINRETURNremainder;ENDRemainder;PROCEDURECompare (dec1, dec2: Decimal): CompareResults;VARcount :INTEGER;BEGINcount := MaxDigits - 1;WHILE(count > 0)AND(dec1.number[count] = dec2.number[count])DODEC(count);END;IFcount < 0THENRETURNequalELSIFdec1.number[count] < dec2.number[count]THENRETURNlessELSERETURNgreater;END;ENDCompare;PROCEDURENeg (dec: Decimal): Decimal;BEGINdec.isNeg :=NOTdec.isNeg;RETURNdec;ENDNeg;PROCEDUREStatus (dec: Decimal): DecState;BEGINRETURNdec.state;ENDStatus;BEGINtheHandler := DefaultHandler;ENDDecimals.

Naturally, there have to be procedures for getting data into and out of the internal form. In this case, these are not located in the ADT definition module, but in two other places. First, one can define fairly straightforward input and output for *Decimal* quantities.

DEFINITIONMODULEDecimalIO; (* by R. Sutcliffe modified 1996 11 04 *)IMPORTIOChan;FROMDecimalsIMPORTDecimal;PROCEDUREReadDecimal (cid : IOChan.ChanId;VARdec : Decimal);PROCEDUREWriteDecimal (cid : IOChan.ChanId; dec : Decimal; width :CARDINAL);ENDDecimalIO.IMPLEMENTATIONMODULEDecimalIO; (* by R. Sutcliffe modified 1996 11 04 *)IMPORTIOChan, TextIO, IOResult;FROMDecimalsIMPORTDecimal, MaxDigits, DecRange, zero, DecState, decPoint;FROMCharClassIMPORTIsNumeric;FROMWholeIOIMPORTWriteCard;FROMIOResultIMPORTReadResults;FROMSTextIOIMPORTWriteChar;IMPORTSWholeIO;TYPEDecString =ARRAYDecRangeOFCHAR; (* exported procs *)PROCEDUREReadDecimal (cid : IOChan.ChanId;VARdec : Decimal);VARtemp : DecString; count, len :CARDINAL; ch :CHAR; res : IOResult.ReadResults;BEGINcount := 0; IOChan.Look (cid, ch, res);IF(res = allRight)THENdec := zero; (* initialize it *) dec.isNeg := (ch = "-")END;IF(ch = "-")OR(ch = "+")THENIOChan.SkipLook (cid, ch, res);END;WHILE(count < MaxDigits)AND(res = allRight)DO(* skips over all non numerics *)IF(IsNumeric (ch))THENtemp [count] := ch;INC(count);END; IOChan.SkipLook (cid, ch, res);END;IF(res = allRight)OR(res = endOfLine)THENlen := count - 1;WHILEcount > 0DODEC(count); dec.number[len - count] :=ORD(temp [count]) -ORD("0");END; (* while *) dec.state := allOK;END; (* if *)ENDReadDecimal;PROCEDUREWriteDecimal (cid : IOChan.ChanId; dec : Decimal; width :CARDINAL);VARcount, scount :CARDINAL; started :BOOLEAN;BEGINstarted :=FALSE;FORcount := MaxDigits-1TO0BY-1DOIF(NOTstarted)AND((dec.number [count] # 0)OR(count = 0))THENstarted :=TRUE;IFdec.isNegAND(width > 1)THENDEC(width);END; (* if dec *)IFwidth = 0THENWriteChar (" ");ELSIFwidth > count + 1THENFORscount := 1TOwidth - count - 1DOWriteChar (" ");END;END;IFdec.isNegTHENWriteChar ("-");END; (* if dec *)END;IFstartedOR(count = 0)THENWriteCard (cid, dec.number [count], 1);ENDEND(* for *)ENDWriteDecimal;ENDDecimalIO.

As was the case with the module *ComplexIO* earlier in the chapter, the corresponding modules for the standard channels are much easier.

DEFINITIONMODULESDecimalIO; (* by R. Sutcliffe modified 1996 11 04 *)FROMDecimalsIMPORTDecimal;PROCEDUREReadDecimal (VARdec : Decimal);PROCEDUREWriteDecimal (dec : Decimal; width :CARDINAL);ENDSDecimalIO.IMPLEMENTATIONMODULESDecimalIO; (* by R. Sutcliffe modified 1996 11 04 *)FROMDecimalsIMPORTDecimal;IMPORTStdChans, DecimalIO;PROCEDUREReadDecimal (VARdec : Decimal);BEGINDecimalIO.ReadDecimal (StdChans.InChan(), dec);ENDReadDecimal;PROCEDUREWriteDecimal (dec : Decimal; width :CARDINAL);BEGINDecimalIO.WriteDecimal (StdChans.OutChan(), dec, width);ENDWriteDecimal;ENDSDecimalIO.

The rest of the problem of moving data to and fro between *Decimal* and other formats is solved in yet another module that employs format or picture strings. Note that the conversion to *Decimal* from strings just scans the string looking for and collecting numeric digits. An alternate method is to specify an input picture, and scan it along with the input string to ensure that the correct format is used.

DEFINITIONMODULEDecimalStr; (* by R. Sutcliffe last modified 1996 11 05 *)FROMDecimalsIMPORTDecimal;PROCEDUREStrToDec (string:ARRAYOFCHAR): Decimal; (* This procedure extracts the digits from any string and converts these into a decimal number. A leading sign is correctly interpreted, but all other non numeric characters are simply ignored. *)PROCEDUREDecToStr (dec: Decimal; picture:ARRAYOFCHAR;VARresult:ARRAYOFCHAR); (* Formats the Decimal according to the picture. Characters with special meaning are: # a leading blank or a digit 9 a leading zero or a digit ! the decimal character defined in the module Decimals (commonly "." or ",") = the sign (+ or -) , the separator defined in the module Decimals (commonly "." or "," or " ") all other characters in the picture string are entered into the result literally *)ENDDecimalStr.

Notice that *StrToDec* is set up as a function, but *DecToStr* is a regular procedure that returns its result in a variable parameter.

**NOTE:** The use of special characters in the mask or picture, and the exact meaning given to these varies from one implementation to another. This usage is rather typical, but not identical to any particular product.

**Example 1: **The number 34235678945 placed into the picture "$###,###,##9!99" would result in the string "$342 356 789.45". If placed instead into the picture "=999,999,999,999" it would result in the string "+ 34 235 678 945". (The definition module is compiled with space as the separator rather than the American comma.)

**Example 2: **If the string "-1.2345" is read by *ReadDecimal* only the digits and sign are stored, so the resulting *Decimal *value placed either into the picture "##!99" or the picture ##.99" would result in the string "123.45". (The extra digit takes up some room at the beginning.) If placed instead into the picture "=##99999!9" it would result in the string "- 01234.5". There are two spaces before the leading zero because there is room for eight figures provided by the mask and only five are needed.

**Example 3: **If the string "23" is read by *ReadDecimal* and the resulting *Decimal *value placed into the picture "##99!99999" the result string is " 00.00023" but if into the picture "##99.##999" the result string is " 00. 023" which is probably not too useful, but is according to the picture.

Here is the implementation module for *DecimalStr*. Once again the commenting is minimal so that the reader may add this apparatus.

IMPLEMENTATIONMODULEDecimalStr; (* by R. Sutcliffe last modified 1996 11 05 *)FROMCharClassIMPORTIsNumeric;FROMDecimalsIMPORTDecimal, MaxDigits, zero, decPoint, separator;PROCEDUREStrToDec (string:ARRAYOFCHAR): Decimal; (* This procedure extracts the digits from any string and converts these into a decimal number. A leading sign is correctly interpreted, but all other non numeric characters are simply ignored. *)VARtemp : Decimal; counts, countd :CARDINAL;BEGINtemp := zero; counts :=LENGTH(string); countd := 0;WHILE(counts > 0)AND(countd < MaxDigits)DODEC(counts);IFIsNumeric (string[counts])THENtemp.number[countd] :=ORD(string [counts]) -ORD("0");INC(countd);END;END;IFstring [0] = "-"THENtemp.isNeg :=TRUE;END;RETURNtemp;ENDStrToDec;PROCEDUREDecToStr (dec: Decimal; picture:ARRAYOFCHAR;VARresult:ARRAYOFCHAR); (* Formats the Decimal according to the picture. Characters with special meaning are: # a leading blank or a digit 9 a leading zero or a digit ! the decimal character defined in the module Decimals (commonly "." or ",") = the sign (+ or -) , the separator defined in the module Decimals (commonly "." or "," or " ") all other characters in the picture string are entered into the result literally *)VARcounts, countd, countr, maxs, maxr, picDigits, pad :CARDINAL; ch :CHAR; decDone :BOOLEAN;BEGINdecDone :=FALSE; maxs :=LENGTH(picture); maxr :=HIGH(result); picDigits := 0;FORcounts := 0TOmaxs - 1DOch := picture [counts];IF(ch = "#")OR(ch = "9")THENINC(picDigits)ENDEND; counts := 0; countd := MaxDigits; countr := 0;WHILE(countd > 0)AND(dec.number [countd-1] = 0)DODEC(countd)END;IFpicDigits > countdTHENpad := picDigits - countd;ELSEpad := 0;END; (* special case zero *)IFcountd = 0THENINC(countd);END; ch := picture [counts];WHILE(counts < maxs)AND(countd > 0)AND(countr < maxr)DOIF(ch = "#")OR(ch = "9")THENIFpad = 0THENDEC(countd); result [countr] :=CHR(dec.number[countd] +ORD("0"));ELSE(* fill in spaces or zeros from # and 9 places not used in dec *)IF(ch = "#")THENresult [countr] := " ";ELSIF(ch = "9")THENresult [countr] := "0";END;DEC(pad);END;IFcountd < picDigitsTHENINC(counts);END;ELSIF(ch = "!")THENresult [countr] := decPoint;INC(counts);ELSIF(ch = ",")THENresult [countr] := separator;INC(counts);ELSIF(ch = "=")THENIFdec.isNegTHENresult [countr] := "-"ELSEresult [countr] := "+"END;INC(counts);ELSEresult [countr] := ch;INC(counts);END;INC(countr); ch := picture [counts];END;WHILE(counts < maxs)AND(countr < maxr)DO(* copy any stuff left in picture; must be literals *) result [countr] := ch;INC(counts);INC(countr); ch := picture [counts];END;IF(countr < maxr)THENresult [countr] := 0C;END;ENDDecToStr;ENDDecimalStr.

As indicated in the examples already discussed, a program can read *Decimal* quantities in the form of strings (perhaps in picture form), assign them to variables of type *Decimal*, manipulate them, and then print them out using pictures (perhaps of a different form than in the way they were entered). Here is an example:

Similarly, one could use this functionality to save and print Social Security (Insurance) or credit card numbers in a form with spaces or dashes at appropriate places.