( Title: Convert among raw fp unformatted hex string,
raw fp data stack, and IEEE 754 formats
File: rawhexfloat.fs
Test file: rawhexfloat-test.fs
Log file: mixfloat.log
Version: 0.9.6
Revised: January 20, 2021
Author: David N. Williams
License: LGPL
We believe any of this code derived from other authors to be either in
the public domain or otherwise compatible with the LGPL. For the sake
of the LGPL, the rest is
)
\ Copyright (C) 2003, 2005, 2009, 2020, 2021 David N. Williams
(
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or at
your option any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
General Public License for more details.
If you take advantage of the option in the LGPL to put a particular
version of this library under the GPL, the author[s] would regard it as
polite if you would put any direct modifications under the LGPL as well,
and include this paragraph in your license notice. A direct
modification is one that enhances or extends the library in line with
its original concept, as opposed to developing a distinct application or
library which might use it.
)
\ ***OVERVIEW
(
This library assumes a machine floating point memory representation
corresponding to an IEEE 754 binary32, binary64, binary80, or
binary128 format. The machine storage format is assumed to be bit-wise
contiguous in memory, all little-endian or all big-endian, and with the
same endianess as machine integers.
This library was formerly part of a larger library in mixfloat.fs,
which aimed to provide access to the bits of IEEE 754 binary
floating-point formats.
The central mxifloat format was a Forth text string version of the IEEE
754 hex-fp format, with a mantissa of hex digits and a 2's exponent of
decimal digits. There were direct conversions between this and what we
called "raw" representations for common choices of the precision and
maximum exponent parameters in the IEEE 754 binary32, binary64,
binary80, and binary128 formats.
There were further conversions between each of the four raw formats and
a corresponding unformatted hex string format, and between the host
machine format and the appropriate one of the four raw or unformatted
string formats.
The conversions between raw and unformatted hex string formats are
relatively simple. They depend only on the respective storage widths,
32, 64, 80, or 124 bits, and are agnostic about sign, exponent, and
mantissa layouts. They require no floating point calculations.
The conversion between the machine format and the appropriate one of the
raw or unformatted string formats is also relatively simple. It does
depend on the layout, but only implicitly, via F@ and F!.
This library covers conversions among the raw, the unformatted hex
string, and the host machine formats, and omits the mixfloat format. It
also does fetches and stores of the raw formats between memory and the
data stack.
Here are more details about the three covered formats.
RAW BINARY CELLS: A faithful representation of any of the four IEEE 754
binary formats which organizes them into sufficiently many contiguous
Forth cells on the data stack or in memory, with the most significant
cell on the top of the stack or lower in memory. Unused bits are
padded by zeroes in the most significant part of the most significant
cell, conforming with the Forth convention for narrow data on the
stack.
When the number of bits per cell versus the number of bits needed for
the native fp representation requires two cells, this is the usual
significance ordering style for double numbers in Forth. For more
than two cells, it extends that style.
This format facilitates multicell operations such as shifting, and is
moderately useful for input and display.
UNFORMATTED HEX STRING: A faithful representation of any of the four
IEEE 754 binary formats as a string of characters containing hex
digits, ordered from the most to the least significant digit. For an
IEEE storage width of x bits, the corresponding hex string contains
exactly x/4 hex characters, plus blank characters. An input string
may contain any number of leading, embedded, or trailing blanks. An
output string is formatted in 4-digit, blank separated groups.
This format is intended for easy input and display of unformatted
binary formats.
MACHINE STORAGE: Assumed to correspond to the indicated storage width
for an IEEE 754 binary32, binary64, binary80, or binary128 format.
Assumed to be bit-wise contiguous in memory, all little-endian or all
big-endian, and with the same endianess as machine integers.
The test file rawhexfloat-test.fs contains syntax examples.
This library uses both the integer and floating-point stacks.
Upon inspection, neither here nor in rawfloat-test.fs do we see
words with mixed data/float inputs, outputs, or intermediate
calculations. Everything should work whether or not the
floating-point stack is separate from the data stack, and has
been checked with both options in pfe. See the section
"ENVIRONMENT" for how to select an integrated stack in pfe.
This library refuses to load if cells are neither 32 nor 64 bits, and it
works with either case.
It also refuses to load if chars are not 8 bits, and if 8 bits is not
the address unit.
It eventually refuses to load if the default float format is not 32, 64,
80, or 128 bits.
There is an environmental dependence on lower case.
Notation:
"s" or "something.s" in stack comments stands for an ANS Forth string
[addr len] pair.
)
BASE @
\ *** PUBLIC WORDS
\ SIGNATURE
\ RAW-HEX-FLOAT
\ USER INTERFACE
\ raw! raw@ raw-drop
\ hex>raw raw>hex raw>f f>raw hex>f f>hex
\ ENVIRONMENTAL CONSTANTS
\ BITS/AU BITS/CELL HEX-DIGS/D
\ BITS/FLOAT LITTLE-ENDIAN? FLITTLE-ENDIAN? (from rawfloat.fs)
\ UTILITY
\ lshift>ud (qlshift)
\ -spaces hex>ud ?hex>ud hex>uq ?hex>uq
\ qfaligned qfalign qfcreate qfpad
\ HEX/RAW CONVERSIONS
\ hex8>raw32 hex16>raw64 hex20>raw80 hex32>raw128
\ raw32>hex8 raw64>hex16 raw80>hex20 raw128>hex32
\ RAW FETCH/STORE
\ raw32! raw64! raw80! raw128!
\ raw32@ raw64@ raw80@ raw128@
\ RAW/FLOAT CONVERSIONS
\ raw32>f raw64>f raw80>f raw128>f
\ f>raw32 f>raw64 f>raw80 f>raw128
\ *** SIGNATURE
: RAW-HEX-FLOAT ;
\ *** ENVIRONMENTAL CONSTANTS
decimal
[UNDEFINED] BITS/AU [IF]
s" ADDRESS-UNIT-BITS" ENVIRONMENT? 0=
[IF] cr .( ***Can't determine ADDRESS-UNIT-BITS.) ABORT [THEN]
CONSTANT BITS/AU
[THEN]
bits/au 1 chars * #8 <> [IF] cr
.( ***1 CHARS is not 8 bits.) ABORT [THEN]
bits/au #8 <> [IF]
cr .( ***bits/au is ) bits/au . .( bits, not 8 ) ABORT [THEN]
[UNDEFINED] BITS/CELL [IF]
bits/au 1 cells * CONSTANT BITS/CELL
[THEN]
bits/cell #32 <> bits/cell #64 <> and [IF]
cr .( ***bits/cell is ) bits/cell . .( bits, not 32 or 64 ) ABORT [THEN]
bits/cell 2/ CONSTANT HEX-DIGS/D
\ *** UTILITY
: lshift>ud ( u +n -- ud' )
(
Assume +n <= BITS/CELL. Logical shift u to the left by +n bits into an
unsigned double number.
)
LOCALS| n u |
n 0= IF u 0 EXIT THEN
n bits/cell = IF 0 u EXIT THEN
u n lshift u bits/cell n - rshift ;
: (qlshift) ( q +n -- q' )
(
Assume +n <= BITS/CELL. Logical shift the quadruple q to the left by +n
bits.
)
LOCALS| n q0 q1 q2 q3 |
n bits/cell = IF 0 ELSE q0 n lshift THEN ( q0'.hi)
q1 n lshift>ud ( q0'.hi q1'.hi q0'.lo)
rot + ( q0') to q0
( q1'.hi) q2 n lshift>ud ( q1'.hi q2'.hi q1'.lo)
rot + ( q1') to q1
( q2'.hi) q3 n lshift>ud ( q2'.hi q3'.hi q2'.lo)
rot + q1 q0 ;
: -spaces ( s -- s' )
(
Remove all blanks from the ANS Forth string s and leave the compacted
string s'. The new string body address is at PAD. This works even if
the input string s starts somewhere in PAD, but then it may get
partially written over.
)
pad >r
BEGIN
( len) dup 0>
WHILE
over c@ bl <>
IF over c@ r@ c! r> 1+ >r THEN
1 /string
REPEAT 2drop
pad r> pad - ;
: hex>ud ( s -- ud flag )
(
If the string contains only hexadecimal digits with no sign, or is
empty, leave true and the unsigned double number conversion [0 if there
are no digits]. Otherwise leave an undefined ud result and false.
)
base @ >r hex
0 0 2swap >number ( ud rest.s)
nip 0= r> base ! ;
: ?hex>ud ( s -- ud ) hex>ud 0= ABORT" ***Not a hex string" ;
: hex>uq ( s -- uq flag )
(
If the string contains only hexadecimal digits with no sign, or is
empty, leave true and the unsigned quadruple number conversion [0 if
there are no digits]. Otherwise leave an undefined uq result and false.
)
( len) dup hex-digs/d <=
IF over 0 2swap ELSE hex-digs/d - 2dup + hex-digs/d THEN ( hi.s lo.s)
LOCALS| lo.len lo.addr hi.len hi.addr |
base @ >r hex
0 0 lo.addr lo.len >number
IF
( lo.ud addr) 0 false
ELSE
( lo.ud addr) drop 0 0 hi.addr hi.len >number
( lo.ud hi.ud addr' len') nip 0=
THEN
r> base ! ;
: ?hex>uq ( s -- uq ) hex>uq 0= ABORT" ***Not a hex string" ;
\ *** HEX/RAW CONVERSIONS
: num>s ( n -- n.s )
(
Convert the number according to the current BASE to a Forth string in
transient memory. Traditional.
)
<# dup >r abs s>d #s r> sign #> ;
(
Because char-counted strings are deprecated, we use "measured strings"
[mstrings], which are strings with an implementation-defined count-field
size. Our library defaults to cell-size.
In stack comments, a $ suffix indicates the address of an mstring.
)
: m$>s ( m$ -- s ) \ COUNT for cell count-field
>r r@ cell+ ( addr) r> @ ( len) ;
: s-m$+ ( s m$ -- )
(
Append the Forth string to the mstring. Transliteration of Wil Baden's
APPEND.
)
2dup 2>r m$>s + swap move 2r> +! ;
create pad$ 128 chars allot
: 0pad$ ( -- ) 0 pad$ ! ;
: pad$>s ( -- ) pad$ m$>s ;
: pad+ ( s -- ) pad$ s-m$+ ;
: bl+ ( -- ) s" " pad+ ;
: digs+ ( +num #digs -- )
( #digs) >r num>s
r> over ( len) - dup 0< ABORT" ***Too many digits."
( #digs-len) 0 ?DO s" 0" pad+ LOOP
( num.s) pad+ ;
: bl-4digs+ ( +4.digit.num -- ) bl+ #4 digs+ ;
: hdigs+ ( -- ) base @ >r hex digs+ r> base ! ;
: bl-4hdigs+ ( -- ) base @ >r hex bl-4digs+ r> base ! ;
\ HEX8>RAW32, HEX16>RAW64, HEX20>RAW80, HEX32>RAW128
(
Translate a Forth string representation of a raw floating-point datum to
the corresponding raw format on the data stack. These words are for
convenience in expressing raw data without concern for right to left
stack notation, and with embedded blanks for readability. They all
ABORT if the string does not contain exactly the required number of hex
digits.
)
: hex8>raw32 ( hex8.s -- raw32 )
-spaces dup #8 <> ABORT" ***Not 8 hex digits."
?hex>ud ( hi) drop ;
: hex16>raw64 ( hex16.s -- raw64 )
-spaces dup #16 <> ABORT" ***Not 16 hex digits."
?hex>ud [ bits/cell #64 = ] [IF] drop [THEN] ;
: hex20>raw80 ( hex20.s -- raw80 )
-spaces dup #20 <> ABORT" ***Not 20 hex digits."
?hex>uq [ bits/cell #64 = ] [IF] 2drop [ELSE] drop [THEN] ;
: hex32>raw128 ( hex32.s -- raw128 )
-spaces dup #32 <> ABORT" ***Not 32 hex digits."
?hex>uq [ bits/cell #64 = ] [IF] 2drop [THEN] ;
\ RAW32>HEX8, RAW64>HEX16, RAW80>HEX20, RAW128>HEX32
(
Translate the raw formats to hex strings of hex digits formatted
in 4-digit, blank-separated groups.
)
bits/cell #32 = [IF]
: raw32>hex8 ( raw32 -- hex8.s ) \ raw32 has 1 cell
0pad$ #16 lshift>ud #4 hdigs+
#16 lshift>ud bl-4hdigs+ ( ud'.lo) drop pad$>s ;
: raw64>hex16 ( raw64 -- hex16.s ) \ raw64 has 2 cells
0pad$ #16 lshift>ud #4 hdigs+
#16 lshift>ud bl-4hdigs+ ( ud'.lo) drop
#16 lshift>ud bl-4hdigs+
#16 lshift>ud bl-4hdigs+ ( ud'.lo) drop pad$>s ;
: raw80>hex20 ( raw80 -- hex20.s ) \ raw80 has 2.5 cells
#16 lshift \ move top.cell.lo16 to top.cell.hi16
0pad$ #16 lshift>ud #4 hdigs+ ( ud'.lo) drop
#16 lshift>ud bl-4hdigs+
#16 lshift>ud bl-4hdigs+ ( ud'.lo) drop
#16 lshift>ud bl-4hdigs+
#16 lshift>ud bl-4hdigs+ ( ud'.lo) drop pad$>s ;
: raw128>hex32 ( raw128 -- hex32.s ) \ raw128 has 4 cells
0pad$ #16 lshift>ud #4 hdigs+
#16 lshift>ud bl-4hdigs+ ( ud'.lo) drop
#3 0 DO #16 lshift>ud bl-4hdigs+
#16 lshift>ud bl-4hdigs+ ( ud'.lo) drop
LOOP pad$>s ;
[ELSE] \ 64-BITS/CELL
: raw32>hex8 ( raw32 -- hex8.s ) \ raw32 has 1 cell
0pad$ #48 lshift>ud #4 hdigs+
#16 lshift>ud bl-4hdigs+ ( ud'.lo) drop pad$>s ;
: raw64>hex16 ( raw64 -- hex16.s ) \ raw64 has 1 cells
0pad$ #16 lshift>ud 4 hdigs+
#3 0 DO #16 lshift>ud bl-4hdigs+ LOOP ( ud'.lo) drop pad$>s ;
: raw80>hex20 ( raw80 -- hex20.s ) \ raw80 has 1.25 cells
#48 lshift \ move top.cell.lo16 to top.cell.hi16
0pad$ #16 lshift>ud #4 hdigs+ ( ud'.lo) drop
#4 0 DO #16 lshift>ud bl-4hdigs+ LOOP ( ud'.lo) drop pad$>s ;
: raw128>hex32 ( raw128 -- hex32.s ) \ raw128 has 2 cells
0pad$ #16 lshift>ud #4 hdigs+
#3 0 DO #16 lshift>ud bl-4hdigs+ LOOP ( ud'.lo) drop
#4 0 DO #16 lshift>ud bl-4hdigs+ LOOP ( ud'.lo) drop pad$>s ;
[THEN]
\ *** USER INTERFACE
[UNDEFINED] RAW-FLOAT [IF] INCLUDE rawfloat.fs [THEN]
: hex>raw ( hex.s -- raw.default )
[ bits/float #32 = ] [IF] hex8>raw32 [ELSE]
[ bits/float #64 = ] [IF] hex16>raw64 [ELSE]
[ bits/float #80 = ] [IF] hex20>raw80 [ELSE]
[ bits/float #128 = ] [IF] hex32>raw128 [THEN] [THEN] [THEN] [THEN] ;
: raw>hex ( raw64.default -- hex.s )
[ bits/float #32 = ] [IF] raw32>hex8 [ELSE]
[ bits/float #64 = ] [IF] raw64>hex16 [ELSE]
[ bits/float #80 = ] [IF] raw80>hex20 [ELSE]
[ bits/float #128 = ] [IF] raw128>hex32 [THEN] [THEN] [THEN] [THEN] ;
\ default floats
: hex>f ( hex.default.s -- f: r ) hex>raw raw>f ;
: f>hex ( f: r -- s: hex.default.s ) f>raw raw>hex ;
BASE !