CHAPTER 8.  NUMERIC CONVERSIONS

A very important task of the text interpreter is to convert numbers from a human readable form into a machine readable form and vice versa.  Forth allows its user the luxury of using any number base, be it decimal, octal, hexadecimal, binary, radix 36, radix 50, etc.  He can also switch from one base to another without much effort.  The secret lies in a user variable named BASE which holds the base value used to convert a machine binary number for output, and to convert a user input number to binary.  The default value stored in BASE is decimal 10.  It can be changed by

 

: HEX    10H BASE ! ;   to hexadecimal,

 

: OCTAL  8H BASE ! ;    to octal, and

 

: DECIMAL  0AH BASE ! ;     back to decimal.

 

The simple command n BASE ! can store any reasonable number into BASE to effect numeric conversions. 

The word NUMBER is the workhorse converting ASCII represented numbers to binary and pushing the result on the data stack.  The word sequence <# #S #> converts a number on top of the stack to its ASCII equivalent for output to terminal.  These words and their close relatives are discussed in this Chapter.  The overall view on the process of converting a string to its binary numeric representation is shown in Fig.  8. 

 

: (NUMBER)      d1 addr1 --- d2 addr2

 

Run-time routine of number conversion.   Convert an ASCII text beginning at addr1+1 according to BASE.   The result is accumulated with d1 to become d2.  addr2 is the  address of the first unconvertable digit. 

 

BEGIN

    1+ DUP >R        Save addr1+1, address of the first digit, on return stack.

    C@                    Get a digit

    BASE @            Get the current base

    DIGIT                A primitive.  ( c n1 -- n2 tf or ff )
Convert the character c according to base n1 to a binary number n2 with a true flag on top of stack.  If the digit is an invalid character, only a false flag is left on stack.

WHILE                   Successful conversion, accumulate into d1.

    SWAP                 Get the high order part of d1 to the top.

    BASE @ U*       Multiply by base value

    DROP                 Drop the high order part of the product

    ROT                   Move the low order part of d1 to top of stack

    BASE @ U*       Multiply by base value

    D+                      Accumulate result into d1

    DPL @ 1+          See if DPL is other than -1

    IF                       DPL is not -1, a decimal point was encountered

        1 DPL +!        Increment DPL, one more digit to right of decimal point

    ENDIF

    R>                     Pop addr1+1 back to convert the next digit.

REPEAT                If an invalid digit was found, exit the loop here.  Otherwise repeat the conversion until the string is exhausted.

R>                         Pop return stack which contains the address of the first non-convertable digit, addr2.

;

 

: NUMBER            addr -- d

 

Convert character string at addr with a preceding byte count  to signed double integer number, using the current base.  If  a decimal point is encountered in the text, its position will  be given in DPL.  If numeric conversion is not possible, issue  an error message. 

 

0 0 ROT                Push two zero's on stack as the initial value of d .

DUP 1+ C@          Get the first digit

2DH =                   Is it a - sign?

DUP >R                 Save the flag on return stack.

+                            If the first digit is -, the flag is 1, and addr+1 points to the second digit.  If the first digit is not -, the flag is 0.
addr+0 remains the same, pointing to the first digit.

-1                          The initial value of DPL

BEGIN                  Start the conversion process

    DPL !                Store the decimal point counter

    (NUMBER)       Convert one digit after another until an invalid char occurs.
Result is accumulated into d .

    DUP C@           Fetch the invalid digit

    BL -                   Is it a blank?

WHILE                  Not a blank, see if it is a decimal point

    DUP C@           Get the digit again

    2EH -                 Is it a decimal point?

    0 ?ERROR         Not a decimal point.  It is an illegal character for a number.
Issue an error message and quit.

    0                        A decimal point was found.  Set DPL to 0 the next time.

REPEAT                Exit here if a blank was detected.  Otherwise repeat the conversion process.

DROP                    Discard addr on stack

R>                         Pop the flag of - sign back

IF DMINUS           Negate d if the first digit is a - sign.

ENDIF

;                            All done.  A double integer is on stack.

 

: <#                --

 

Initialize conversion process by setting HLD to PAD.   The conversion is done on a double integer, and produces  a text string at PAD. 

 

PAD                      PAD is the scratch pad address for text output, 68 bytes above the dictionary head HERE .

HLD !                    HLD is a user variable holding the address of the last character in the output text string.

;

 

 

: HOLD          c --

 

Used between <# and #> to insert an ASCII character  c into a formatted numeric output string. 

 

-1 HLD +!            Decrement HLD .

HLD @ C!            Store character c into PAD .

;

 

: #                 d1 -- d2

 

Divide d1 by current base.  The remainder is converted to  an ASCII character and appended to the output text string.   The quotient d2 is left on stack. 

 

BASE @                Get the current base.

M/MOD                 Divide d1 by base.  Double integer quotient is on top of data stack and the remainder below it.

ROT                      Get the remainder over to top.

9 OVER <              If remainder is greater than 9,

IF 7 + ENDIF         make it an alphabet.

30H +                    Add 30H to form the ASCII representation of a digit.
0 to 9 and A to F (or above).

HOLD                   Put the digit in PAD in a reversed order.  HLD is decremented before the digit is moved.

;

 

: #S                d1 -- d2

 

Using # to generate the complete ASCII string representing  the number d1 until d2 is zero.  Used between <# and #> . 

 

BEGIN

    #           Convert one digit.

    OVER OVER   Copy d2

    OR 0=       d2=0?

UNTIL           Exit if d2=0, conversion done.  Otherwise repeat.

;

 

: SIGN          n d -- d

 

Store an ASCII - sign before the converted number string  in the text output buffer if n is negative.  Discard n but  leave d on stack. 

 

ROT 0<          Is n negative?

IF

    2DH HOLD        Add - sign to text string.

ENDIF

;

 

: #>                d -- addr count

 

Terminate numeric conversion by dropping off d, leaving the  text buffer address and character count on stack to be typed. 

 

DROP DROP        Discard d.

 HLD @                Fetch the address of the last character in the text string.

PAD OVER -        Calculate the character count of the text string. 

;

 

: CR                --

 

Transmit a carriage-return and a line-feed to terminal. 

 

0DH EMIT            Carriage-Return

0AH EMIT            Line-Feed

;

 

: SPACE         --

 

Transmit an ASCII blank to the terminal. 

 

BL EMIT ;

 

: SPACES            n --

 

Transmit n blanks to the terminal. 

 

0 MAX           If n<0, make it 0.

-DUP                DUP n only if n>0.

IF

    0 DO            Do n times

        SPACE   Type a space on terminal

    LOOP

ENDIF

;

 

Now we have all the necessary utility words to construct an ASCII text string representing a double integer in whatever the current base, we can show some words which type out numbers in different output formats. 

 

: D.R               d n --

 

Print a signed double number d right justified in a field  of n characters. 

 

>R                         Store n on return stack.

SWAP OVER        Save the high order part of d under d, to be used by SIGN to add a - sign to a negative number.

DABS                    Convert d to its absolute value.

<# #S SIGN #>       Convert the absolute value to ASCII text with proper sign.

R>                          Retrieve n from the return stack.

OVER - SPACES    Fill the output field with preceding blanks.

TYPE                      Type out the number.

;

 

Other numeric output words are derived from D.R , and not much comments are necessary. 

 

: D.                d --

 

Print a signed double integer according to current base,  followed by only one blank.  This is called the free format output. 

 

0               0 field width.

D.R

;

 

: .R                n1 n2 --

 

Print a signed integer n1 right justified in a field of  n2 characters. 

 

>R                         Save n2 on return stack.

S->D                      A primitive word.  Extend the single integer to a double integer with the same sign.

R> D.R                   Formatted output.

;

 

: .                 n --

 

Print signed integer n in free format followed by one blank. 

 

S->D                Sign-extend the single integer. 

D.              Free format output.

;

 

: ?                 addr --

 

Print the value contained in addr in free format according  to the current base. 

 

@ .                 Fetch the number and type it out.

 

;

 

A very useful word in programming and debugging a Forth program is the word DUMP , which dumps out an area of memory as numbers for the user to inspect.  It is also useful in cases to show large blocks of data  stored in contiguous memory locations.  These data can be dumped out on the terminal. 

 

: DUMP          addr n --

 

Print the contents of n memory cells beginning at addr.  Both addresses and contents are shown in the current base. 

 

0 DO                     DO n times

    CR                     Start a new line.

    DUP 8 .R           Print the address of the first cell in this line.

    8 0 DO              Print the contents of 8 cells in one line.

        DUP              Copy addr on stack.

        @                  Get the data,

        8 .R               Formatted print in fields of 8 characters.

        2+                 Address of next data to be printed.

    LOOP

8 +LOOP               Increment the outer loop count by 8 and repeat.

DROP                   Discard the last address on the stack.

;