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
![]()
Page updated: 24 Jan 2008