17.6 Binary Coded Decimal Fixed Point Types

An important variation on the idea of storing cardinals in a digit by digit fashion and then using a picture to print out the numbers with a decimal point is to store the digits along with a decimal point position. In other words, each stored item is thought of as a decimal fraction with a specific number of places after the decimal. Again, the storage representation is exact, so no rounding off errors take place. One could modify the definition of the last section as:

  Decimal = 
      state : DecState;
      isNeg : BOOLEAN;
      number : ARRAY DecRange OF Digit;
      decPlace : DecRange;

The code for arithmetic operations will now be considerably more complicated, because the decimal place has to be taken into consideration each time, and each operand can have it in a different place. The complexity of doing arithmetic also slows things down considerably. Yet such a numeric type is attractive for business arithmetic, and some vendors do wish to include such a facility. Indeed, some hardware has built in support in the machine language to which one is compiling for such a type.

Ordinarily, as shown in Chapter 8, each machine nibble can code one hexadecimal digit in the range of 0 - F (hex) or 0 - 15 (decimal). Arithmetic is performed by routines that handle carries from nibble to nibble automatically. Thus, F + E produces 1C. The number of nibbles that make up a numeric type (and for which such carrying is performed) determine how many hexadecimal digits can be stored by that type.

Now, if a nibble can store a hexadecimal digit, it can surely store a decimal digit, and do so with room to space, because there are only ten possibilities rather than sixteen. Many computers allow for a switch to be set to tell the nibbles to do their carry in a decimal fashion rather than a hexadecimal fashion. Thus, the digits A,B,C,D,E, and F are not used when arithmetic is in this mode, and the digit each nibble stores can be thought of as a decimal digit. True, there is a little waste memory space this way, but this technique does answer the need of being able to store each decimal digit individually and therefore to do arithmetic without round off errors.

The name of any data type stored and manipulated in decimal digits at the binary level is binary coded decimal or BCD for short.

Because putting the machine into BCD arithmetic mode (and changing it back again) requires a machine language instruction to set the mode switch, and because not all machines will support BCD at all, the BCD type is not in Modula-2 per se. However, some vendors do add such support to their otherwise standard system. When this is done, client modules will normally have to include


before making any use of the type. Because this is a numeric type, if the vendor does go to the trouble to provide it, arithmetic operations should work using the normal operators (overloaded) on items of type BCD from this point. However the details will vary from one vendor to another.

17.6.1 BCD Support in p1 Modula-2 (Optional)

NOTE: Information in this section is implementation-specific to one product. The modules described here may exist in other forms, with other names, or not at all in other Modula-2 systems.

The p1 compiler is an example of a package that does include a BCD type that has to be imported from SYSTEM but for which the arithmetic operators are overloaded. As one might expect from an otherwise ISO standard package, there is a module for doing I/O that has a rather standard-looking interface:

(* Input and output of bcd numbers in decimal text form *)
(* the text form of a signed bcd number is
     ["+" | "-"], decimal digit, {decimal digit}, [".", {decimal digit}] ["$"] *)
PROCEDURE ReadBcd (cid: IOChan.ChanId; VAR bcd: BCD);
  (* Skips leading spaces and removes any remaining characters that form part of a signed bcd number.  A corresponding value is assigned to the parameter bcd.  The read result is set to the value allRight, outOfRange, wrongFormat, endOfLine, or endOfInput. *)
PROCEDURE WriteFixed (cid: IOChan.ChanId; bcd: BCD; place: INTEGER; width: CARDINAL);
  (* Writes the value of the parameter bcd in fixed-point text form, rounded to the given place relative to the decimal point, in a field of the given minimum width. *)
  (* Examples of fixed point output:
     value:     3923009   3.923009   0.0003923009
     -5         3920000          0              0
     -2         3923010          0              0
     -1         3923009          4              0
      0        3923009.         4.             0.
      1       3923009.0        3.9            0.0
      4    3923009.0000     3.9230         0.0004

There is also a module SBcdIO. Naturally, there are facilities like those in the last section, though a little more sophisticated, for converting to and from formatted strings.

(* BCD/string conversions *)
IMPORT ConvTypes, BcdConv;
  ConvResults = ConvTypes.ConvResults; (* strAllRight, strOutOfRange, strWrongFormat, strEmpty *)
  BcdFormat = BcdConv.BcdFormat; (* formatOk, decMarker, missingChar, illegalValue *)
(* the string form of a signed bcd number is
     ["+" | "-"], decimal digit, {decimal digit}, [".", {decimal digit}] ["$" *)
PROCEDURE StrToBcd (str: ARRAY OF CHAR; VAR bcd: BCD; VAR res: ConvResults);
  (* Ignores any leading spaces in str.  If the subsequent characters in str are in the format of a signed bcd number, assigns a corresponding value to bcd.  Assigns a value indicating the format of str to res. *)
PROCEDURE BcdToFixed (bcd: BCD; place: INTEGER; VAR str: ARRAY OF CHAR);
  (* Converts the value of bcd to fixed-point string form, rounded to the given place relative to the decimal point, and copies the possibly truncated result to str. *)
PROCEDURE BcdToFree (bcd: BCD; format: ARRAY OF CHAR;
                     VAR str: ARRAY OF CHAR; VAR formatResult: BcdFormat);
  (* If format is a well-format format string and bcd can be represented within this format, converts bcd to the given format, and assings the possibly truncated result to str, and "formatOk" to formatResult. Otherwise assigns a value indicating the error to formatResult. *)
END BcdStr.

The lower level module that this one imports from defines the format or picture string type and also provides facilities for checking on the validity of such a string before making use of it.

IMPORT ConvTypes;
  ConvResults = ConvTypes.ConvResults;
  BcdFormat = (
    formatOk,          (* correct conversion format *)
    decMarker,          (* none or more than one marker for the position
                         of the decimal point specified  *)
    missingChar,        (* missing characters after "=" or "\" *)
    illegalValue        (* a value that is too great to be represented with this format *)

The format string accepted by the conversion routines has the following rules:

  Z : digit, leading " "
  # : digit, leading " "
  9 : digit, leading "0"
  = : sign, the two following characters represent "+" and "-"
  - : same as "= -"
  + : same as "=+-"
  ! : marks the position of the decimal point (no required representation)
  , : same as "!\,"
  . : same as "!\."
  \ : escape character, the follow character is printed without
  other characters: copied to the result string as they are.

  format: " ###\.###\.###\.##9,999 =HS"
       23456.78  is converted to :  "    .   . 23.456,780 H"
       -0.123  is converted to :  "    .   .   .  0,123 S"

       23456.78  formatResult becomes illegalValue
       -0.123  is converted to :  "-000.12300"

      23456.78  is converted to :  "DM    23456,78"
      -0.123  is converted to :  "DM -       ,12"

  "DM - #####9,99"
      23456.78  is converted to :  "DM    23456,78"
      -0.123  is converted to :  "DM -      0,12"

PROCEDURE ScanBcd (inputCh: CHAR; VAR chClass: ConvTypes.ScanClass;
           VAR nextState: ConvTypes.ScanState);
  (* Represents the start state of a finite state scanner for bcd numbers - assigns class of inputCh to chClass and a procedure representing the next state to nextState. *)
PROCEDURE FormatBcd (str: ARRAY OF CHAR): ConvResults;
  (* Returns the format of the string value for conversion to BCD *)
  (* If str is well-formed, returns the value corresponding to the bcd number string value str, otherwise an exception is raised. *)
PROCEDURE LengthFixedBcd (bcd: BCD; place: INTEGER): CARDINAL;
  (* Returns the number of characters in the fixed-point string representation of bcd rounded to the given place relative to the decimal point. *)
PROCEDURE TestFreeFormat (format: ARRAY OF CHAR): BcdFormat ;
  (* Tests wether format is a format string accepted by conversion rouines, value checks cannot be done. *)

PROCEDURE LengthFreeBcd (bcd: BCD; format: ARRAY OF CHAR): CARDINAL;
  (* If format is a well-formed format string and bcd can be represented within this format, returns the number of characters in the given string representation of bcd, otherwise an exception is raised. *)
PROCEDURE IsBcdConvException (): BOOLEAN;          (*V5.0b6*)
END BcdConv.

Much of this is more or less as expected. Procedures like ScanBcd are a little odd, but a fuller explanation of such scanners can be found in a later section of this chapter. As can be seen, the string formatting rules are a little different (and more complicated) than in the simple example of the previous section, but there is no great uniformity in such matters, and the example begun there was deliberately kept as simple as possible so as not to confuse the concepts.