REPRESENT and MAX-FLOAT-DIGITS: A Complete Solution

Revision 2.1  2008-01-23

The following builds upon an earlier paper 'REPRESENT - A Report and
ANS FORTH Proposal' which details portability problems surrounding
floating point conversion function REPRESENT.

---------------------------------------------------------------------


MAX-FLOAT-DIGITS  (updated 2006-10-01)

Problem:

There is no easy way to determine the maximum number of usable digits
that can be output from a floating point number.

System implementors know and use this value internally (e.g. for Intel
80x87 FPU's it is typically 18 digits for a 10-byte float).  Portable
applications also need to know this value in order to determine:

- the number of digits a given forth can usefully display
- minimum buffer size to allocate for REPRESENT
- usable values to use with SET-PRECISION

Solution:

Add to Table 12.2 - Environmental query strings

String             Value       Constant?   Meaning
                   data type
------             ---------   ---------   -------

MAX-FLOAT-DIGITS   u           yes         largest number of usable
                                           digits available from
                                           REPRESENT

MAX-FLOAT-DIGITS is thus a measure of a system's floating point
output precision.  It should not be confused with the intrinsic
precision of a floating number though in many instances they would
be similar.

Implementation:

The value to be used for MAX-FLOAT-DIGITS is implementation dependent.
In all likelihood it has already been chosen by the system designer
and appears as a constant within the code for REPRESENT.

Typically MAX-FLOAT-DIGITS is the number of digits at which REPRESENT
stops outputting digits from the mantissa and begins to output zeros
instead.  While not foolproof, the following tests may assist in
determining a suitable value for MAX-FLOAT-DIGITS.  Ideally all tests
would work and give the same result.

  : TEST1  ( -- )  \ scan forward 80 chars
    80 SET-PRECISION
    1E0 3E0 F/  PAD 80 REPRESENT 0= ABORT" failed" DROP DROP
    PAD 0  80 0 DO  OVER  I CHARS +  C@ [CHAR] 3 - IF LEAVE
    THEN  1+  LOOP  SWAP DROP  CR ." Max usable digits = " . ;

  : TEST2  ( -- )  \ scan forward PRECISION chars
    80 SET-PRECISION
    1E0 3E0 F/  PAD PRECISION REPRESENT 0= ABORT" failed" DROP DROP
    PAD 0  PRECISION 0 DO  OVER  I CHARS +  C@ [CHAR] 3 - IF LEAVE
    THEN  1+  LOOP  SWAP DROP  CR ." Max usable digits = " . ;

  : TEST3  ( -- )  \ trim trailing '0's
    80 SET-PRECISION
    1E0 3E0 F/  PAD 80 REPRESENT 0= ABORT" failed" DROP DROP
    PAD 80  BEGIN  DUP WHILE  1- 2DUP CHARS +  C@ [CHAR] 0 -
    UNTIL  1+  THEN  SWAP DROP  CR ." Max usable digits = " . ;

Summary:

MAX-FLOAT-DIGITS is an important system constant which has hitherto
been unavailable.  Its introduction will allow floating point
applications to be written in a more portable manner.

MAX-FLOAT-DIGITS is useful in its own right and should be considered
independently of any other proposal.

Addendum - The FVG Floating Point Standard has a similar (?) function
called F#PLACES .

---------------------------------------------------------------------


REPRESENT - An alternative proposal

Problem:

The ANS Forth-94 REPRESENT is quite loose in its definition and
has resulted in a number of portability issues.  Many of these were
discussed in the document:

ftp://ftp.taygeta.com/pub/Forth/Applications/ANS/Represent_11.txt

A proposal for a revised REPRESENT was included.  While it fixed
several issues, the problem of string truncation remained being
deemed "too hard" to resolve.

With the addition of MAX-FLOAT-DIGITS a complete solution is now
possible - one that is simple and will work with the majority of
existing applications.

Solution:

REPRESENT  (updated 2008-01-23)

Definition:

   12.6.1.xxxx  REPRESENT
   FLOATING

      ( c-addr n1 -- n2 flag1 flag2 )  (F: r -- )
      or ( r c-addr n1 -- n2 flag1 flag2 )

   At c-addr, place the character-string external representation
   of the significand of the floating-point number r.  Return the
   decimal base exponent as n2, the sign as flag1 and valid result
   as flag2.

   If n1 is greater than zero the significand is rounded to n1 digits
   and represented as a decimal fraction with an implied decimal
   point to the left of the first digit.  The first digit is zero
   only if all digits are zero.  If n1 exceeds MAX-FLOAT-DIGITS then
   zeros shall follow.

   If n1 is zero the significand is rounded to a whole number, either
   one or zero, and represented as above.  If n1 is negative the
   significand is rounded to zero.

   Rounding follows the round to nearest rule; n2 is adjusted, if
   necessary, to correspond to the rounded magnitude of the
   significand.  If r is zero or evaluates to zero after rounding,
   then n2 is 1 and the sign is implementation-defined.

   If flag2 is true then r was in the implementation-defined range
   of floating-point numbers.  If flag1 is true then r was negative.

   When flag2 is false, n2 and flag1 are implementation-defined, as
   are the contents of c-addr.  The string at c-addr shall consist
   of graphic characters left-justified with unused positions to
   the right filled with space characters.

   In all cases the number of characters returned shall be the
   greater of n1 or MAX-FLOAT-DIGITS.

   An ambiguous condition exists if the value of BASE is not decimal
   ten.

Notes:

   Existing applications will be unaffected provided a buffer not less
   than MAX-FLOAT-DIGITS characters has been allocated.  As of this
   date the number of applications reported to fail this requirement
   is 1.

   New applications need only ensure a minimum of MAX-FLOAT-DIGITS
   is allocated to the REPRESENT buffer.

---------------------------------------------------------------------


Addendum

1. Sample REPRESENT implementation  (updated 2008-01-23)

   \ Sample REPRESENT implementation
   \ Assumes flag2 is always true and MAX-FLOAT-DIGITS can be held
   \ as a double number.  "Negative zero" is not implemented.

   S" MAX-FLOAT-DIGITS" ENVIRONMENT? 0= [IF]
     CR .( MAX-FLOAT-DIGITS not found ) ABORT
   [THEN]
   CONSTANT  maxdigits  \ maximum usable digits

   2VARIABLE expsgn     \ exponent, sign

   : REPRESENT  ( c-addr n1 -- n2 flag1 flag2 ) ( F: r -- )
   \ 2>R  FDUP nan? IF  ( r was not a number )
   \   FDROP  2R> maxdigits MAX
   \   OVER SWAP BLANK
   \   S" NAN" ROT SWAP CMOVE
   \   0 FALSE FALSE  EXIT
   \ THEN  2R>
     2DUP maxdigits MAX [CHAR] 0 FILL
     DUP 0< IF
       2DROP FDROP  0.
     ELSE
       maxdigits MIN  2>R
       FDUP F0<  0 expsgn 2!
       FABS  FDUP F0= 0=
       BEGIN  WHILE
         FDUP 1.0E F< 0= IF
           10.0E F/
           1
         ELSE
           FDUP 0.1E F< IF
             10.0E F*
             -1
           ELSE
             0
           THEN
         THEN
         DUP expsgn +!
       REPEAT
       1.0E  R@ 0 ?DO 10.0E F* LOOP  F*
       FROUND F>D
       2DUP <# #S #>  DUP R@ - expsgn +!
       2R>  ROT MIN 1 MAX CMOVE
     THEN
     D0= IF  1 0  ELSE  expsgn 2@ SWAP  THEN  \ 0.0E fix-up
     TRUE ;

2. Sample applications

   Example 1
   ---------
   Create a floating point output function to display fixed point
   notation and handle all conditions including not-a-number.

   The following code assumes PRECISION is available and its value
   does not exceed MAX-FLOAT-DIGITS.  If it is unavailable then
   replace PRECISION with maxdigits.

   DECIMAL

   S" MAX-FLOAT-DIGITS" ENVIRONMENT? 0= [IF]
     CR .( MAX-FLOAT-DIGITS not found ) ABORT
   [THEN] CONSTANT maxdigits

   CREATE buf  maxdigits CHARS ALLOT

   : .mant  ( u -- )
     buf OVER TYPE  [CHAR] . EMIT
     buf PRECISION  ROT /STRING  TYPE ;

   : F.  ( r -- )
     buf PRECISION REPRESENT IF
       IF [CHAR] - EMIT THEN
       DUP >R  0 PRECISION 1+ WITHIN IF
         R> .mant
       ELSE
         1 .mant  [CHAR] E EMIT  R> 1- .
       THEN
     ELSE
       2DROP  buf maxdigits -TRAILING TYPE SPACE
     THEN ;

   \ Test the function

   : TEST1  ( -- )
     CR 1.23456E20   F.
     CR 1.23456E9    F.
     CR 1.23456E8    F.
     CR 1.23456E7    F.
     CR 1.23456E6    F.
     CR 1.23456E5    F.
     CR 1.23456E4    F.
     CR 1.23456E3    F.
     CR 1.23456E2    F.
     CR 1.23456E1    F.
     CR 1.23456E0    F.
     CR 0.0E         F.
     CR 1.23456E-1   F.
     CR 1.23456E-2   F.
   \ CR 1.0E 0.0E F/ F.  ."  {+INF on 8087}"
   \ CR 0.0E 0.0E F/ F.  ."  {-NAN on 8087}"
   ;

   TEST1

   Example 2
   ---------
   Implement the FORTH-94 floating point display function FE.
   Check that it performs correctly when PRECISION is less than
   the number of digits to be displayed in the significand.

   DECIMAL

   S" MAX-FLOAT-DIGITS" ENVIRONMENT? 0= [IF]
     CR .( MAX-FLOAT-DIGITS not found ) ABORT
   [THEN] CONSTANT maxdigits

   CREATE buf  maxdigits CHARS ALLOT

   : .mant  ( u -- )
     buf OVER TYPE  [CHAR] . EMIT
     buf PRECISION  ROT OVER MIN  /STRING  TYPE ;

   : FE.  ( r -- )
     buf PRECISION REPRESENT IF
       IF [CHAR] - EMIT THEN
       1-  S>D 3 FM/MOD  3 *  >R
       1+ .mant  [CHAR] E EMIT  R> .
     ELSE
       2DROP  buf maxdigits -TRAILING TYPE  SPACE
     THEN ;


   : TEST2  ( -- )
     PRECISION >R
     2 SET-PRECISION  467.8E  CR FE.  ."  {should be 470.E0 }"
     R> SET-PRECISION ;

   TEST2

   Example 3
   ---------
   Implements a suite of floating point output functions.
   (updated 2008-01-23)

   \
   \ FPOUT.F   version 3.4
   \
   \ A Forth floating point output words package
   \
   \ Main words:
   \
   \   Compact   Formatted   String
   \   -------   ---------   ------
   \   FS.       FS.R        (FS.)     Scientific
   \   FE.       FE.R        (FE.)     Engineering
   \   F.        F.R         (F.)      Fixed-point
   \   G.        G.R         (G.)      General
   \
   \   FDP ( -- a-addr )
   \
   \   A variable controlling decimal point display.  If zero
   \   then trailing decimal points are not shown. If non-zero
   \   (default state) the decimal point is always displayed.
   \
   \   FECHAR ( -- a-addr )
   \
   \   A variable containing the output character used to
   \   indicate the exponent. Default is 'E'.
   \
   \   FEDIGITS ( -- a-addr )
   \
   \   A variable containing the minimum number of digits
   \   output for the exponent. Must be 2 or more. Default
   \   is 2. Does not affect compact modes.
   \
   \ Notes:
   \
   \ Display words that specify the number of places after
   \ the decimal point may use the value -1 to force compact
   \ mode.  Compact mode displays all significant digits
   \ with redundant zeros and signs removed.  FS. FE. F. G.
   \ are displayed in compact mode.
   \
   \ The character string returned by (FS.) (FE.) (F.) (G.)
   \ resides in the pictured-numeric output area.
   \
   \ An ambiguous condition exists if: BASE is not decimal;
   \ character string exceeds pictured-numeric output area;
   \ PRECISION is set greater than MAX-FLOAT-DIGITS.
   \
   \ For use with separate or common stack floating point
   \ Forth models.
   \
   \ This code is PUBLIC DOMAIN.  Use at your own risk.
   \
   \ *****************************************************
   \ This version of FPOUT requires REPRESENT conform to
   \ the specification proposed here:
   \
   \  ftp://ftp.taygeta.com/pub/Forth/Applications/ANS/
   \  Represent_21.txt  (2008-01-23)
   \
   \ If your Forth does not have a compliant REPRESENT
   \ then use FPOUT v2.2 instead.
   \ *****************************************************
   \
   \ History:
   \
   \ v3.1  13-Nov-06  es   Demo for REPRESENT proposal.
   \ v3.2  05-Jun-07  es   Changed default to trailing
   \                       decimal point on.
   \ v3.3  19-Nov-07  es   Add FECHAR FEDIGITS. Fix zero
   \                       sign in (F.) F.R
   \ v3.4  23-Jan-08  es   Updated to REPRESENT spec 2.1
   \

   CR .( Loading FPOUT v3.4  23-Jan-08 ... ) CR

   DECIMAL

   \ Compile application

   CREATE FDP  2 CELLS ALLOT
   VARIABLE FECHAR
   VARIABLE FEDIGITS

   \ ******************  USER OPTIONS  *******************

   1 FDP !                 \ trailing decimal point control
   2 FEDIGITS !            \ minimum exponent digits
   CHAR E FECHAR !         \ output character for exponent

   \ *****************************************************

   S" MAX-FLOAT-DIGITS" ENVIRONMENT? 0= [IF]
     CR .( MAX-FLOAT-DIGITS not found ) ABORT
   [THEN]  ( u )

   \ Define PRECISION SET-PRECISION if not present

   [UNDEFINED] PRECISION [IF]

   ( u ) DUP VALUE PRECISION ( -- u )

   : SET-PRECISION  ( u -- )
     DUP  1 [ PRECISION 1+ ] LITERAL  WITHIN AND
     ?DUP IF  TO PRECISION  THEN ;

   [THEN]

   ( u ) CONSTANT mp#      \ maximum usable precision

   CREATE fbuf  mp# CHARS ALLOT

   0 VALUE ex#             \ exponent
   0 VALUE sn#             \ sign
   0 VALUE ef#             \ exponent factor  1=FS. 3=FE.
   0 VALUE pl#             \ +n  places right of decimal point
                           \ -1  compact display

   \ get exponent, sign, flag2
   : (f1)  ( F: r -- r ) ( -- exp sign flag2 )
     FDUP fbuf PRECISION REPRESENT ;

   \ apply exponent factor
   : (f2)  ( exp -- offset exp2 )
     S>D ef# FM/MOD ef# * ;

   \ float to ascii
   : (f3)  ( F: r -- ) ( places -- c-addr u flag )
     TO pl#  (f1) NIP AND ( exp & flag2 )
     pl# 0< IF
       DROP PRECISION
     ELSE
       ef# 0> IF  1- (f2) DROP 1+  THEN  pl# +
     THEN  PRECISION MIN  fbuf SWAP REPRESENT >R
     TO sn#  TO ex#  fbuf mp# -TRAILING  R> <# ;

   \ insert exponent
   : (f4)  ( exp -- )
     DUP  ABS S>D  pl# 0< 0=  DUP >R  IF  FEDIGITS @
     1 DO # LOOP  THEN  #S 2DROP  DUP SIGN  0< 0=
     R> AND IF  [CHAR] + HOLD  THEN  FECHAR @ HOLD ;

   \ insert digit and update flag
   : (f5)  ( char -- )
     HOLD  1 FDP CELL+ ! ;

   \ insert string
   : (f6)  ( c-addr u -- )
     0 MAX  BEGIN  DUP  WHILE  1- 2DUP CHARS + C@ (f5)
     REPEAT 2DROP ;

   \ insert '0's
   : (f7)  ( n -- )
     0 MAX 0 ?DO [CHAR] 0 (f5) LOOP ;

   \ insert sign
   : (f8)  ( -- )
     sn# SIGN  0 0 #> ;

   \ trim trailing '0's
   : (f9)  ( c-addr u1 -- c-addr u2 )
     pl# 0< IF
       BEGIN  DUP WHILE  1- 2DUP CHARS +
       C@ [CHAR] 0 -  UNTIL  1+  THEN
     THEN ;

   : (fa)  ( n -- n n|pl# )
     pl# 0< IF  DUP  ELSE  pl#  THEN ;

   \ insert fraction string n places right of dec. point
   : (fb)  ( c-addr u n -- )
     0 FDP CELL+ !
     >R (f9)  R@ +
     (fa) OVER - (f7)     \ trailing 0's
     (fa) MIN  R@ - (f6)  \ fraction
     R> (fa) MIN (f7)     \ leading 0's
     FDP 2@ OR IF
       [CHAR] . HOLD
     THEN ;

   \ split string into integer/fraction parts at n and insert
   : (fc)  ( c-addr u n -- )
     >R  2DUP R@ MIN 2SWAP R> /STRING  0 (fb) (f6) ;

   \ exponent form
   : (fd)  ( F: r -- ) ( n factor -- c-addr u )
     TO ef#  (f3) IF  ex# 1- (f2) (f4) 1+ (fc) (f8)  THEN ;

   \ display c-addr u right-justified in field width u2
   : (fe)  ( c-addr u u2 -- )
     OVER - SPACES TYPE ;

   \ These are the main words

   \ Convert real number r to a string c-addr u in scientific
   \ notation with n places right of the decimal point.

   : (FS.)  ( F: r -- ) ( n -- c-addr u )
     1 (fd) ;

   \ Display real number r in scientific notation right-
   \ justified in a field width u with n places right of the
   \ decimal point.

   : FS.R  ( F: r -- ) ( n u -- )
     >R (FS.) R> (fe) ;

   \ Display real number r in scientific notation followed by
   \ a space.

   : FS.  ( F: r -- )
     -1 0 FS.R SPACE ;

   \ Convert real number r to a string c-addr u in engineering
   \ notation with n places right of the decimal point.

   : (FE.)  ( F: r -- ) ( n -- c-addr u )
     3 (fd) ;

   \ Display real number r in engineering notation right-
   \ justified in a field width u with n places right of the
   \ decimal point.

   : FE.R  ( F: r -- ) ( n u -- )
     >R (FE.) R> (fe) ;

   \ Display real number r in engineering notation followed
   \ by a space.

   : FE.  ( F: r -- )
     -1 0 FE.R SPACE ;

   \ Convert real number r to string c-addr u in fixed-point
   \ notation with n places right of the decimal point.

   : (F.)  ( F: r -- ) ( n -- c-addr u )
     0 TO ef#  (f3) IF
       ex#  DUP mp# > IF
         fbuf 0 ( dummy ) 0 (fb)
         mp# - (f7) (f6)
       ELSE
         DUP 0> IF
           (fc)
         ELSE
           ABS (fb) 1 (f7)
         THEN
       THEN (f8)
     THEN ;

   \ Display real number r in fixed-point notation right-
   \ justified in a field width u with n places right of the
   \ decimal point.

   : F.R  ( F: r -- ) ( n u -- )
     >R (F.) R> (fe) ;

   \ Display real number r in fixed-point notation followed
   \ by a space.

   : F.  ( F: r -- )
     -1 0 F.R SPACE ;

   \ Convert real number r to string c-addr u with n places
   \ right of the decimal point.  Fixed-point is used if the
   \ exponent is in the range -4 to 5 otherwise use scientific
   \ notation.

   : (G.)  ( F: r -- ) ( n -- c-addr u )
     >R  (f1) NIP AND [ -4 1+ ] LITERAL [ 5 2 + ] LITERAL WITHIN
     R> SWAP IF  (F.)  ELSE  (FS.)  THEN ;

   \ Display real number r right-justified in a field width u
   \ with n places right of the decimal point.  Fixed-point
   \ is used if the exponent is in the range -4 to 5 otherwise
   \ use scientific notation.

   : G.R  ( F: r -- ) ( n u -- )
     >R (G.) R> (fe) ;

   \ Display real number r followed by a space.  Fixed-point
   \ is used if the exponent is in the range -4 to 5 otherwise
   \ use scientific notation.

   : G.  ( F: r -- )
     -1 0 G.R SPACE ;

   CR  FDP @ [IF]
     CR .( Decimal point always displayed.  Use  0 FDP ! )
     CR .( or  FDP OFF  to disable trailing decimal point. )
   [ELSE]
     CR .( Trailing decimal point not displayed.  Use )
     CR .( 1 FDP !  or  FDP ON  for FORTH-94 compliance. )
   [THEN]  CR

   \ ******************  DEMONSTRATION  ******************

   0 [IF]

   CR .( Loading demo words... ) CR
   CR .( TEST1  formatted, n decimal places )
   CR .( TEST2  compact & right-justified )
   CR .( TEST3  display FS. )
   CR .( TEST4  display F. )
   CR .( TEST5  display G. )
   CR .( TEST6  display 8087 non-numbers ) CR
   CR .( 'n PLACES' sets decimal places for TEST1. )
   CR .( SET-PRECISION sets maximum significant )
   CR .( digits displayable. )
   CR CR

   [UNDEFINED] F, [IF]
   : F,  ( r -- )  FALIGN HERE 1 FLOATS ALLOT F! ;
   [THEN]

   CREATE f-array  \ floating-point numbers array

   FALIGN HERE
   1.23456E-16  F,
   1.23456E-11  F,
   1.23456E-7   F,
   1.23456E-6   F,
   1.23456E-5   F,
   1.23456E-4   F,
   1.23456E-3   F,
   1.23456E-2   F,
   1.23456E-1   F,
   0.E0         F,
   1.23456E+0   F,
   1.23456E+1   F,
   1.23456E+2   F,
   1.23456E+3   F,
   1.23456E+4   F,
   1.23456E+5   F,
   1.23456E+6   F,
   1.23456E+7   F,
   1.23456E+11  F,
   1.23456E+16  F,
   HERE SWAP -  1 FLOATS /  CONSTANT #numbers

   : do-it  ( xt -- )
     #numbers 0 DO
       f-array FALIGNED I FLOATS +
       OVER >R  F@  CR  R> EXECUTE
     LOOP DROP ;

   2VARIABLE (dw)
   : d.w  ( -- dec.places width )  (dw) 2@ ;
   : PLACES ( places -- ) d.w SWAP DROP (dw) 2! ;
   : WIDTH  ( width -- )  d.w DROP SWAP (dw) 2! ;

   5 PLACES  19 WIDTH

   : (t1)  ( r -- )
     FDUP d.w FS.R  FDUP d.w F.R  FDUP d.w G.R  d.w FE.R ;

   : TEST1  ( -- )
     CR ." TEST1   right-justified, formatted ("
     d.w DROP 0 .R ."  decimal places)" CR
     ['] (t1) do-it  CR ;

   : (t2)  ( r -- )
     FDUP -1 d.w NIP FS.R  FDUP -1 d.w NIP F.R
     FDUP -1 d.w NIP G.R        -1 d.w NIP FE.R ;

   : TEST2  ( -- )
     CR ." TEST2   right-justified, compact" CR
     ['] (t2) do-it  CR ;

   : TEST3  ( -- )
     CR ." TEST3   FS."
     CR ['] FS. do-it  CR ;

   : TEST4  ( -- )
     CR ." TEST4   F."
     CR ['] F. do-it  CR ;

   : TEST5  ( -- )
     CR ." TEST5   G."
     CR ['] G. do-it  CR ;

   : TEST6  ( -- )
     PRECISION >R  1 SET-PRECISION
     CR ." TEST6   8087 non-numbers  PRECISION = 1" CR
     CR 1.E0 0.E0 F/  FDUP G.
     CR FNEGATE            G.
     CR 0.E0 0.E0 F/  FDUP G.
     CR FNEGATE            G.
     CR
     R> SET-PRECISION ;

   [ELSE]

   CR .( To compile demonstration words TEST1..TEST6 )
   CR .( enable conditional in FPOUT source. ) CR

   [THEN]

   \ end

---------------------------------------------------------------------

History
-------
2006-10-01  Revision of REPRESENT proposal (version 1.1 to 2.0)
2006-10-30  Add addendum and examples
2006-10-31  Add FPOUT example
2006-11-27  Example corrected
2008-01-23  Revised proposal (version 2.0 to 2.1)
            Specification extended to include negative values
            of rounding specifier n1.
Top    Home    Forth

em.gif (457 bytes)


stats count

Page updated: 24 Jan 2008