\ @(#) floats.fth 98/02/26 1.4 17:51:40
\ High Level Forth support for Floating Point
\
\ Author: Phil Burk and Darren Gibbs
\ Copyright 1994 3DO, Phil Burk, Larry Polansky, David Rosenboom
\
\ Permission to use, copy, modify, and/or distribute this
\ software for any purpose with or without fee is hereby granted.
\
\ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
\ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
\ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
\ THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
\ CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
\ FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
\ CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
\ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
\
\ 19970702 PLB Drop 0.0 in REPRESENT to fix  0.0 F.
\ 19980220 PLB Added FG. , fixed up large and small formatting
\ 19980812 PLB Now don't drop 0.0 in REPRESENT to fix  0.0 F.  (!!!)
\              Fixed F~ by using (F.EXACTLY)

ANEW TASK-FLOATS.FTH

: FALIGNED  ( addr -- a-addr )
    1 floats 1- +
    1 floats /
    1 floats *
;

: FALIGN    ( -- , align DP )
    dp @ faligned dp !
;

\ account for size of create when aligning floats
here
create fp-create-size
fp-create-size swap - constant CREATE_SIZE

: FALIGN.CREATE  ( -- , align DP for float after CREATE )
    dp @
    CREATE_SIZE +
    faligned
    CREATE_SIZE -
    dp !
;

: FCREATE  ( <name> -- , create with float aligned data )
    falign.create
    CREATE
;

: FVARIABLE ( <name> -- ) ( F: -- )
    FCREATE 1 floats allot
;

: FCONSTANT
    FCREATE here   1 floats allot   f!
    DOES> f@
;

: F0SP ( -- ) ( F: ? -- )
    fdepth 0 max  0 ?DO fdrop LOOP
;

\ Floating point structure member.
: FFIELD:   ( n1 "name" -- n2 ; addr1 -- addr2 )
    FALIGNED 1 FLOATS +FIELD
;

\ Convert between single precision integer and floating point
: S>F ( s -- ) ( F: -- r )
    s>d d>f
;
: F>S ( -- s ) ( F: r -- )
    f>d d>s
;

: (F.EXACTLY) ( r1 r2 -f- flag , return true if encoded equally ) { | caddr1 caddr2 fsize fcells }
    1 floats -> fsize
    fsize cell 1- + cell 1- invert and  \ round up to nearest multiple of stack size
    cell / -> fcells ( number of cells per float )
\ make room on data stack for floats data
    fcells 0 ?DO 0 LOOP
    sp@ -> caddr1
    fcells 0 ?DO 0 LOOP
    sp@ -> caddr2
\ compare bit representation
    caddr1 f!
    caddr2 f!
    caddr1 fsize caddr2 fsize compare 0=
    >r fcells 2* 0 ?DO drop LOOP r>  \ drop float bits
;

: F~ ( -0- flag ) ( r1 r2 r3 -f- )
    fdup F0<
    IF
        frot frot  ( -- r3 r1 r2 )
        fover fover ( -- r3 r1 r2 r1 r2 )
        f- fabs    ( -- r3 r1 r2 |r1-r2| )
        frot frot  ( -- r3  |r1-r2| r1 r2 )
        fabs fswap fabs f+ ( -- r3 |r1-r2|  |r1|+|r2| )
        frot fabs f* ( -- |r1-r2|  |r1|+|r2|*|r3| )
        f<
    ELSE
        fdup f0=
        IF
            fdrop
            (f.exactly)  \ f- f0=  \ 19980812 Used to cheat. Now actually compares bit patterns.
        ELSE
            frot frot  ( -- r3 r1 r2 )
            f- fabs    ( -- r3 |r1-r2| )
            fswap f<
        THEN
    THEN
;

\ FP Output --------------------------------------------------------
fvariable FVAR-REP  \ scratch var for represent
: REPRESENT { c-addr u | n flag1 flag2 --  n flag1 flag2 , FLOATING } ( F: r -- )
    TRUE -> flag2   \ FIXME - need to check range
    fvar-rep f!
\
    fvar-rep f@ f0<
    IF
        -1 -> flag1
        fvar-rep f@ fabs fvar-rep f!   \ absolute value
    ELSE
        0 -> flag1
    THEN
\
    fvar-rep f@ f0=
    IF
\       fdrop \ 19970702 \ 19980812 Remove FDROP to fix "0.0 F."
        c-addr u [char] 0 fill
        0 -> n
    ELSE
        fvar-rep f@
        flog
        fdup f0< not
        IF
            1 s>f f+ \ round up exponent
        THEN
        f>s -> n
\ ." REP - n = " n . cr
\ normalize r to u digits
        fvar-rep f@
        10 s>f u n - s>f f** f*
        1 s>f 2 s>f f/ f+   \ round result
\
\ convert float to double_int then convert to text
        f>d
\ ." REP - d = " over . dup . cr
        <#  u 1- 0 ?DO # loop #s #>  \ ( -- addr cnt )
\ Adjust exponent if rounding caused number of digits to increase.
\ For example from 9999 to 10000.
        u - +-> n
        c-addr u move
    THEN
\
    n flag1 flag2
;

variable FP-PRECISION

\ Set maximum digits that are meaningful for the precision that we use.
1 FLOATS 4 / 7 * constant FP_PRECISION_MAX

: PRECISION ( -- u )
    fp-precision @
;
: SET-PRECISION ( u -- )
    fp_precision_max min
    fp-precision !
;
7 set-precision

32 constant FP_REPRESENT_SIZE
64 constant FP_OUTPUT_SIZE

create FP-REPRESENT-PAD FP_REPRESENT_SIZE allot  \ used with REPRESENT
create FP-OUTPUT-PAD FP_OUTPUT_SIZE allot     \ used to assemble final output
variable FP-OUTPUT-PTR            \ points into FP-OUTPUT-PAD

: FP.HOLD ( char -- , add char to output )
    fp-output-ptr @ fp-output-pad 64 + <
    IF
        fp-output-ptr @ tuck c!
        1+ fp-output-ptr !
    ELSE
        drop
    THEN
;
: FP.APPEND { addr cnt -- , add string to output }
    cnt 0 max   0
    ?DO
        addr i + c@ fp.hold
    LOOP
;

: FP.STRIP.TRAILING.ZEROS ( -- , remove trailing zeros from fp output )
    BEGIN
        fp-output-ptr @ fp-output-pad u>
        fp-output-ptr @ 1- c@ [char] 0 =
        and
    WHILE
        -1 fp-output-ptr +!
    REPEAT
;

: FP.APPEND.ZEROS ( numZeros -- )
    0 max   0
    ?DO [char] 0 fp.hold
    LOOP
;

: FP.MOVE.DECIMAL   { n prec -- , append with decimal point shifted }
    fp-represent-pad n prec min fp.append
    n prec - fp.append.zeros
    [char] . fp.hold
    fp-represent-pad n +
    prec n - 0 max fp.append
;

: (EXP.) ( n -- addr cnt , convert exponent to two digit value )
    dup abs 0
    <# # #s
    rot 0<
    IF [char] - HOLD
    ELSE [char] + hold
    THEN
    #>
;

: FP.REPRESENT ( -- n flag1 flag2 ) ( r -f- )
;

: (FS.)  ( -- addr cnt ) ( F: r -- , scientific notation )
    fp-output-pad fp-output-ptr !  \ setup pointer
    fp-represent-pad   precision  represent
\ ." (FS.) - represent " fp-represent-pad precision type cr
    ( -- n flag1 flag2 )
    IF
        IF [char] - fp.hold
        THEN
        1 precision fp.move.decimal
        [char] e fp.hold
        1- (exp.) fp.append \ n
    ELSE
        2drop
        s" <FP-OUT-OF-RANGE>" fp.append
    THEN
    fp-output-pad fp-output-ptr @ over -
;

: FS.  ( F: r -- , scientific notation )
    (fs.) type space
;

: (FE.)  ( -- addr cnt ) ( F: r -- , engineering notation ) { | n n3 -- }
    fp-output-pad fp-output-ptr !  \ setup pointer
    fp-represent-pad precision represent
    ( -- n flag1 flag2 )
    IF
        IF [char] - fp.hold
        THEN
\ convert exponent to multiple of three
        -> n
        n 1- s>d 3 fm/mod \ use floored divide
        3 * -> n3
        1+ precision fp.move.decimal \ amount to move decimal point
        [char] e fp.hold
        n3 (exp.) fp.append \ n
    ELSE
        2drop
        s" <FP-OUT-OF-RANGE>" fp.append
    THEN
    fp-output-pad fp-output-ptr @ over -
;

: FE.  ( F: r -- , engineering notation )
    (FE.) type space
;

: (FG.)  ( F: r -- , normal or scientific ) { | n n3 ndiff -- }
    fp-output-pad fp-output-ptr !  \ setup pointer
    fp-represent-pad precision represent
    ( -- n flag1 flag2 )
    IF
        IF [char] - fp.hold
        THEN
\ compare n with precision to see whether we do scientific display
        dup precision >
        over -3 < OR
        IF  \ use exponential notation
            1 precision fp.move.decimal
            fp.strip.trailing.zeros
            [char] e fp.hold
            1- (exp.) fp.append \ n
        ELSE
            dup 0>
            IF
\ POSITIVE EXPONENT - place decimal point in middle
                precision fp.move.decimal
            ELSE
\ NEGATIVE EXPONENT - use 0.000????
                s" 0." fp.append
\ output leading zeros
                negate fp.append.zeros
                fp-represent-pad precision fp.append
            THEN
            fp.strip.trailing.zeros
        THEN
    ELSE
        2drop
        s" <FP-OUT-OF-RANGE>" fp.append
    THEN
    fp-output-pad fp-output-ptr @ over -
;

: FG.  ( F: r -- )
    (fg.) type space
;

: (F.)  ( F: r -- , normal or scientific ) { | n n3 ndiff prec' -- }
    fp-output-pad fp-output-ptr !  \ setup pointer
    fp-represent-pad  \ place to put number
    fdup flog 1 s>f f+ f>s precision max
    fp_precision_max min dup -> prec'
    represent
    ( -- n flag1 flag2 )
    IF
\ add '-' sign if negative
        IF [char] - fp.hold
        THEN
\ compare n with precision to see whether we must do scientific display
        dup fp_precision_max >
        IF  \ use exponential notation
            1 precision fp.move.decimal
            fp.strip.trailing.zeros
            [char] e fp.hold
            1- (exp.) fp.append \ n
        ELSE
            dup 0>
            IF
    \ POSITIVE EXPONENT - place decimal point in middle
                prec' fp.move.decimal
            ELSE
    \ NEGATIVE EXPONENT - use 0.000????
                s" 0." fp.append
    \ output leading zeros
                dup negate precision min
                fp.append.zeros
                fp-represent-pad precision rot + fp.append
            THEN
        THEN
    ELSE
        2drop
        s" <FP-OUT-OF-RANGE>" fp.append
    THEN
    fp-output-pad fp-output-ptr @ over -
;

: F.  ( F: r -- )
    (f.) type space
;

: F.S  ( -- , print FP stack )
    ." FP> "
    fdepth 0>
    IF
        fdepth 0
        DO
            cr?
            fdepth i - 1-  \ index of next float
            fpick f. cr?
        LOOP
    ELSE
        ." empty"
    THEN
    cr
;

\ FP Input ----------------------------------------------------------
variable FP-REQUIRE-E   \ must we put an E in FP numbers?
false fp-require-e !   \ violate ANSI !!

: >FLOAT { c-addr u | dlo dhi u' fsign flag nshift -- flag }
    u 0= IF false exit THEN
    false -> flag
    0 -> nshift
\
\ check for minus sign
    c-addr c@ [char] - =     dup -> fsign
    c-addr c@ [char] + = OR
    IF   1 +-> c-addr   -1 +-> u   \ skip char
    THEN
\
\ convert first set of digits
    0 0 c-addr u >number -> u' -> c-addr -> dhi -> dlo
    u' 0>
    IF
\ convert optional second set of digits
        c-addr c@ [char] . =
        IF
            dlo dhi c-addr 1+ u' 1- dup -> nshift >number
            dup nshift - -> nshift
            -> u' -> c-addr -> dhi -> dlo
        THEN
\ convert exponent
        u' 0>
        IF
            c-addr c@ [char] E =
            c-addr c@ [char] e =  OR
            IF
                1 +-> c-addr   -1 +-> u'   \ skip E char
                u' 0>
                IF
                    c-addr c@ [char] + = \ ignore + on exponent
                    IF
                        1 +-> c-addr   -1 +-> u'   \ skip char
                    THEN
                    c-addr u' ((number?))
                    num_type_single =
                    IF
                       nshift + -> nshift
                       true -> flag
                    THEN
                ELSE
                    true -> flag   \ allow "1E"
                THEN
            THEN
        ELSE
\ only require E field if this variable is true
            fp-require-e @ not -> flag
        THEN
    THEN
\ convert double precision int to float
    flag
    IF
        dlo dhi d>f
        10 s>f nshift s>f f** f*   \ apply exponent
        fsign
        IF
            fnegate
        THEN
    THEN
    flag
;

3 constant NUM_TYPE_FLOAT   \ possible return type for NUMBER?

: (FP.NUMBER?)   ( $addr -- 0 | n 1 | d 2 | r 3 , convert string to number )
\ check to see if it is a valid float, if not use old (NUMBER?)
    dup count >float
    IF
        drop NUM_TYPE_FLOAT
    ELSE
        (number?)
    THEN
;

defer fp.old.number?
variable FP-IF-INIT

: FP.TERM    ( -- , deinstall fp conversion )
    fp-if-init @
    IF
        what's  fp.old.number? is number?
        fp-if-init off
    THEN
;

: FP.INIT  ( -- , install FP converion )
    fp.term
    what's number? is fp.old.number?
    ['] (fp.number?) is number?
    fp-if-init on
    ." Floating point numeric conversion installed." cr
;

FP.INIT
if.forgotten fp.term


0 [IF]

23.8e-9 fconstant fsmall
1.0 fsmall f- fconstant falmost1
." Should be 1.0 = " falmost1 f. cr

: TSEGF  ( r -f- , print in all formats )
." --------------------------------" cr
    34 0
    DO
        fdup fs. 4 spaces  fdup fe. 4 spaces
        fdup fg. 4 spaces  fdup f.  cr
        10.0 f/
    LOOP
    fdrop
;

: TFP
    1.234e+22 tsegf
    1.23456789e+22 tsegf
    0.927 fsin 1.234e+22 f* tsegf
;

[THEN]
