retroforth/example/FloatVar.forth
crc 9b740f56c5 examples: updated FloatVar.forth from Kiyoshi
FossilOrigin-Name: fcd98b59f1ccff5f5e4edf2d5ec47998ea70f66c43fe9694429954cb60738e83
2019-03-03 02:34:20 +00:00

274 lines
7.3 KiB
Forth

## Helpers
~~~
:s:shout (s-) '!_ s:prepend s:put nl 'Stack_:_ s:put dump-stack nl ;
{{
'Depth var
:message (-)
'abort_with__trail__invoked s:shout nl
'Do__reset__to_clear_stack. s:put nl ;
:put-name (a-) fetch d:lookup-xt
dup n:-zero? [ d:name s:put nl ] [ drop ] choose ;
---reveal---
:trail repeat pop put-name again ;
:abort (-?) depth !Depth message trail ;
:s:abort (s-) 's:abort_:_ s:prepend s:put nl abort ;
}}
{{
:f:dump-astack (-)_dump_alternative_stack
f:adepth [ [ f:pop ] times ] sip 'fa_ s:put [ f:dup f:put sp f:push ] times ;
:dump-stacks (-) dump-stack nl f:dump-stack nl f:dump-astack ;
---reveal---
:s (-)___Stack dump-stacks ;
}}
{{
:f:reset (-|float:?-) f:depth [ f:drop ] times ;
:f:areset (-|alt:?-) f:depth [ f:pop f:drop ] times ;
:reset.nf (?-|float:?-) reset f:reset f:areset ;
---reveal---
:r (?-)__Reset reset.nf ;
}}
~~~
## Description
This implements a means of encoding floating point values
into signed integer cells. The technique is described in
the paper titled "Encoding floating point numbers to shorter
integers" by Kiyoshi Yoneda and Charles Childers.
This will extend the `f:` vocabulary and adds a new `e:` vocabulary.
Here `e:` stands for encoded.
## Code & Commentary
Define some constants. The range is slightly reduced from
the standard integer range as the smallest value is used
for NaN.
~~~
n:MAX n:dec 'e:MAX const
n:MAX n:dec n:negate 'e:MIN const
n:MIN 'e:NAN const
n:MAX 'e:INF const
n:MAX n:negate 'e:-INF const
~~~
~~~
:e:n? (e-f) e:MIN n:inc e:MAX n:dec n:between? ;
:e:max? (e-f) e:MAX eq? ;
:e:min? (e-f) e:MIN eq? ;
:e:zero? (e-f) n:zero? ;
:e:nan? (e-f) e:NAN eq? ;
:e:inf? (e-f) e:INF eq? ;
:e:-inf? (e-f) e:-INF eq? ;
:e:clip (e-e) e:MIN e:MAX n:limit ;
~~~
Since 32-bit cell takes about 9 decimal digits, if you set
`[ .1e5 ] f:E1 set-hook`
you will have 4 decimal digits left for the integer part of the encoded number,
which corresponds to 8 decimal digits decoded.
Encode/decode words to secure dynamic range.
This portion is the essence of the method.
~~~
:f:E1 (-|f:-n)_e-unit_in_float hook .1.e5 ; (decimal_digits_to_shift_left
:f:-shift (|f:n-n)_shift_left f:E1 f:* ;
:f:+shift (|f:n-n)_shift_right f:E1 f:/ ;
:f:signed-sqrt (|f:n-n) f:dup f:sign f:abs f:sqrt n:to-float f:* ;
:f:+encode (|f:n-n) f:signed-sqrt f:-shift ;
:f:-encode (|f:n-n) f:dup f:sign f:+shift f:dup f:* n:to-float f:* ;
:f:signed-square (|f:n-n) f:dup f:sign f:dup f:* n:to-float f:* ;
~~~
Deal with special cases.
~~~
:f:to-e (-e|f:n-)
f:dup f:nan? [ f:drop drop e:NAN ] if;
f:dup f:inf? [ f:drop drop e:INF ] if;
f:dup f:-inf? [ f:drop drop e:-INF ] if;
f:+encode f:round f:to-number e:clip (e
e:MIN [ f:drop ] case
e:MAX [ f:drop ] case ;
:e:to-f (e-|f:-n)
e:NAN [ drop f:NAN ] case
e:INF [ drop f:INF ] case
e:-INF [ drop f:-INF ] case
n:to-float f:-encode ;
~~~
```
r .-1234.56789 f:to-e s e:to-f s
```
~~~
:f:store (a-|f:n-) [ f:to-e ] dip store ;
:f:fetch (a-|f:-n) fetch e:to-f ;
~~~
~~~
:f:dump-stack f:depth dup [ f:push ] times [ f:pop f:dup f:put sp ] times ;
~~~
~~~
:e:put (e-)
e:MAX [ 'e:MAX s:put ] case
e:MIN [ 'e:MIN s:put ] case
#0 [ 'e:0 s:put ] case
e:NAN [ 'e:NAN s:put ] case
e:INF [ 'e:INF s:put ] case
e:-INF [ 'e:-INF s:put ] case
e:to-f f:put ;
~~~
```
r .-1234.56789 f:to-e e:put
```
## Higher precision variables
Here are words to encode floating point numbers into two cells.
Words defined up to this point store
floating point numbers, which are often 64-bit or 2-cell numbers,
into signed integers, which are always 32-bit or 1-cell numbers.
The encoding causes loss in both precision and dynamic range.
Words defined below encode floating point numbers into
two integers occupying two 32-bit cells.
Two different ways are implemented:
- `w1` uses fixed point encoding.
Splits a floating point number into the integer and the fraction
parts to store them in two cells.
This is faster with a narrower dynamic range.
- `w2` uses sqrt-encoding described in doc/SqrtEncoding.pdf .
This is slower with a relatively wider dynamic range,
but likely not as wide as the original floating point.
Since Retro's cells are 32-bit,
it is more convenient to handle floating point numbers
encoded into single cells than into pairs of cells.
For instance, consider using the `array:` words for vectors.
Hence in many cases the words found by doing
'e: d:words-with
are suitable.
However, sometimes a higher precision is desired at a higher cost.
These are the same as `f:+encode` and `f:-encode` except that they come
without `f:-shift` and `f:+shift` .
~~~
:f:+encode.w2 (|f:n-n) f:dup f:sign f:abs f:sqrt n:to-float f:* ;
:f:-encode.w2 (|f:n-n) f:dup f:sign f:dup f:* n:to-float f:* ;
~~~
```
r .-12345.6789 f:+encode.w2 s f:-encode.w2 s
```
Split encoded floating point numbers into the integer and the fraction parts.
The sign goes to the integer part.
The fractional part is that of the absolute value.
~~~
{{
:f:+shift9 .1.e-9 f:* ;
:f:-shift9 .1.e9 f:* ;
---reveal---
:f:+split (-|f:n-fi)_absFrac.-shift9_signedInt
f:dup f:sign (s|f:_n
f:abs f:dup f:floor f:swap f:over (s|f:_abs.int_abs_abs.int
f:- f:-shift9 (s|f:_abs.int_abs.frac.-shift9
f:swap n:to-float f:* (_|f:_abs.frac.-shift9_signedInt
;
:f:-split (-|f:fi-n)
f:dup f:sign f:abs (s|f:_abs.frac.-shift9_abs.int
f:swap f:+shift9 f:+ n:to-float f:* (_|f:_n
;
}}
~~~
```
r .-123456789.0987654321 f:+split s f:-split s
```
From float to double cells w2 .
And its inverse.
~~~
:f:to-w1 (-fi|f:n-)_frac_sInt f:+split f:to-number f:to-number swap ;
:w1:to-f (fi-|f:-n) swap n:to-float n:to-float f:-split ;
:f:to-w2 (-fi|f:n-)_frac_sInt f:+encode.w2 f:to-w1 ;
:w2:to-f (fi-|f:-n) w1:to-f f:-encode.w2 ;
~~~
```
r .-123456789.0987654321 f:to-w2 s w2:to-f s
```
`f:var1` and `f:var2` take initial values.
~~~
{{
:f:SATURATE1 (|f:-n) n:MAX n:to-float ;
:f:overflow-check1 (|f:n-n)
f:dup f:abs f:SATURATE1 f:gt?
[ f:put nl 'f:var1_overflow s:abort ] if ;
---reveal---
:f:var1 (s-|f:n-) f:overflow-check1 f:to-w1 rot d:create , , ;
:f:@1 (a-|f:-n) fetch-next [ fetch ] dip w1:to-f ;
:f:!1 (a-|f:n-) f:overflow-check1 f:to-w1 rot store-next store ;
}}
~~~
```
r
.-123456789.0987654321 'FVar1 f:var1
&FVar1 f:@1 s
r
.-98765.4321 &FVar1 f:!1
&FVar1 f:@1 s
```
.-9876543210.123456789 &FVar1 f:@1 (This_test_should__abort
.1.e20 &FVar1 f:!1 (This_test_should__abort
~~~
{{
:f:SATURATE2 (|f:-n) n:MAX n:to-float f:dup f:* ;
:f:overflow-check2 (|f:n-n)
f:dup f:abs f:SATURATE2 f:gt?
[ f:put nl 'f:var2_overflow s:abort ] if ;
---reveal---
:f:var2 (s-|f:n-) f:overflow-check2 f:to-w2 rot d:create , , ;
:f:@2 (a-|f:-n) fetch-next [ fetch ] dip w2:to-f ;
:f:!2 (a-|f:n-) f:overflow-check2 f:to-w2 rot store-next store ;
}}
~~~
```
r
.-123456789.0987654321 'FVar2 f:var2
&FVar2 f:@2 s
r
.-9876543210.123456789 &FVar2 f:!2
&FVar2 f:@2 s
```
.1.e20 &FVar2 f:!2 (This_test_should_abort