
FB II Compiler
PG PRO
Debugging
Memory
System
Mathematics
Resources
Disk I/O
Windows
Controls
Menus
Mouse
Keyboard
Text
Fonts
Drawing
Sound
Clipboard
Printing
Communication
ASM |
MATHEMATICS
Convert IEEE float numbers to BCD format
IEEE float format is followings.
4 bytes IEEE float
(MSB) (LSB)
31 30 23 22 0
s |<-eeeeeeee->| |<-ffffffffffffffff->
^
1.
value = (-1)^s * (1+f) * 2^(e-127)
MSB = most significant bit
LSB = last significant bit
s = sign
0 as plus, 1 as minus
e = exponent
e is unsigned 8 bit integer
e = 0 and e = 0xFF have special meaning
f = significant (mantissa)
f is 23 bit fixed floating real number and floating point is normally ^ position, so f takes between 0<= and <1. The MSB of f is always 1 and it is omitted, significant value will be 1+f.
The two routines below (with a short test program) accomplish the conversion in both directions, without screeds of incomprehensible DEF FNs and other support routines. The incomprehensible junk is safely tucked away in the two LOCAL FNs :) Note: the IEEE variable is assumed to be stored in a LONG WORD, which is a handy container for this non-FB variable type.
'------------- IEEE <-> BCD conversion routines ----------
'convert IEEE 32 bit float to FB BCD double precision
LOCAL FN IEEE2BCD#(IEEESing&)
DIM expon, fr&, value#,shift
expon = (IEEESing& AND &7F800000)>>23 ' bits 1-8
fr&=IEEESing& AND &7FFFFF ' bits 9-31
SELECT expon
CASE 0
LONG IF fr&=0 'zero
value#=0#
XELSE ' denormalised
'constant in next line is 2^-149
value#= fr& * 1.4012984643248e-45
END IF
CASE 255 'signed =83 or NaN (can't represent; use =83)
value#=1e9999
CASE ELSE 'normalised
shift=expon-150
value#=fr&+8388608 ' 2^23
LONG IF shift>=0
value#=value#<<shift
XELSE
value#=value#>>-shift
END IF
END SELECT
IF IEEESing&<0 THEN value#=-value#
END FN=value#
'convert FB BCD double precision to IEEE 32 bit float
LOCAL FN BCD2IEEE&(X#)
DIM 31 Temp$
DIM DecRecord.0,DecSgn%,DecExp%,DecStr$;32
DIM Index%, Valid%
DIM extVar.10, IEEESing&
Temp$=STR$(X#)
Index% = 1
` PEA ^Temp$ ;PTR TO Pascal STRING
` PEA ^Index% ;VAR% OFFSET
` PEA ^DecRecord ;PTR TO DEC RECORD
` PEA ^Valid% ;BYTE VALID
` MOVE.W #2,-(SP) ; PSTR2DEC
` DC.W $A9EE
` PEA ^DecRecord ;PTR TO DEC RECORD
` pea ^extVar
` MOVE.W #9,-(SP) ; FDEC2X
` DC.W $A9EB
` pea ^extVar
` pea ^IEEESing&
_myFPXToSng = _FOX2Z_FFSgl ' for ext -> single
` move.w #myFPXToSng,-(sp)
` dc.w FP68K
END FN= IEEESing& ' 32 bit float stored in LONG WORD
'---------MAIN--------------
WINDOW 1
DIM IEEESing&,BCDVar#
DO
PRINT
PRINT "Enter fp number (0 to end) ";
INPUT BCDVar#
LONG IF BCDVar#<>0
IEEESing&=FN BCD2IEEE& ( BCDVar# )
BCDVar#=FN IEEE2BCD# ( IEEESing& )
PRINT BCDVar#
XELSE
END
END IF
UNTIL 0
I have not read FB's BCD manual well, but IEEE special condition is a more complicated procedure, I think.
Sure, the exponent which all of it bits are 1 means NAN (Not a number), however, this must be significant MUST NOT BE 0.
If significant was 0, this means infinity.
And one more, all bits of exponent is 0, normally this means 0 (zero), but this must be all bits significant also must be 0.
if significant was not 0, this means not normalized, which is caused underflow of exponent, for example 1/infinity.
The code that I posted for converting an IEEE float to FB BCD handles these cases correctly, except perhaps for Infinity and NaN (Not_a_Number), which have no special representation in an FB BCD floating point variable. I chose to represent both of them as 1E999. This is an arbitrary decision, but I think not a silly one.
BTW, denormalised numbers (roughly from 1E-38 down to 1E-45) arise not from 1/Infinity but from subtraction of two very small and nearly-equal numbers (such as 2.0001E-38 - 2E-38). This clever convention was adopted so that in IEEE arithmetic A-B=0 if (and only if) A=B.
Dear Robert: Thank you, thank you, THANK YOU for posting your conversion functions. I've been trying to do that for a long time, and your posts enabled my FB programs to neatly interchange data with PC files generated by a certain not-to-be-named DOS data acquisition program that stores its results in IEEE binary format -- sort of. There were a few minor glitches for the IEEE to BCD conversion. At risk of revealing my profound ignorance (yet again), here's a question or two:
Given the structure of "unnamed's" files the simplest solution was to simply read in the IEEE values as 4-byte strings and go from there (using records would have been quite awkward). Strangely, the "unnamed" DOS program appears to store its IEEE values with the byte order reversed (at least compared to equivalent numbers generated with your BCD-to-IEEE function, which works flawlessly). Having fixed that in the for-next loop in the snippet below, your code worked fine except for small-magnitude negative numbers (i.e., close to zero), which were evaluated as 'denormalized' until I inserted the code in "CASE 4" below.
IEEESing&=0:READ#2,in$;4 ' *** read 4 bytes.... ****
FOR w=1 TO 4 ' *** convert to long word ****
i$=MID$(in$,w,1):z=ASC(i$)
SELECT w
CASE 1:IEEESing&=IEEESing&+z
CASE 2:IEEESing&=IEEESing&+z*256
CASE 3:IEEESing&=IEEESing&+z*65536
CASE 4:LONG IF z>128
z=z-128:sign=-1 ' this is used to give the correct sign to the result
XELSE
sign=1
END IF
IEEESing&=IEEESing&+z*16777216
CASE ELSE
END SELECT
NEXT
DIM expon, fr&, value#,shift
<< your code starts here... >>
Do you have an explanation for the reversed byte order, and for the need to insert the additional code to handle small-magnitude negatives?
Profound thanks again....
Welcome to the world of "Big Endian / Little Endian" confusion! We have to live with the fact that microprocessors differ in their handling of byte order. (The PowerPC can be set for either ordering).
Your additional code (i.e. CASE 4) has the effect of masking off the sign bit (bit0 of the byte) and treating all the other bits as components of an unsigned LONG. It seems, though, that you omitted a line to re-incorporate the sign, something like:-
IF sign =-1 THEN IEEESing&=-IEEESing&
I would rather treat the byte reversal in a boring but obvious way:-
LOCAL FN ReadIEEEAndReverseBytes&
DIM IEEESing&,byte0%,byte1%,byte2%,byte3%
READ#2,IEEESing&
byte0%=PEEK(@IEEESing&)
byte1%=PEEK(@IEEESing&+1)
byte2%=PEEK(@IEEESing&+2)
byte3%=PEEK(@IEEESing&+3)
POKE @IEEESing&,byte3%
POKE @IEEESing&+1,byte2%
POKE @IEEESing&+2,byte1%
POKE @IEEESing&+3,byte0%
END FN=IEEESing&
which also has the advantage of being _much_ faster than your method.
You could call it like this:
myVar#=FN IEEE2BCD#(FN ReadIEEEAndReverseBytes&)
where FN IEEE2BCD# is the function exactly as I posted it earlier in this thread.
[Mild commercial plug follows]
Since this thread arose in the context of converting data originating in an "unnamed" DOS data acquisition system, I may be permitted a small plug for the ADInstruments PowerLab (formerly MacLab) systems. The acquisition hardware works on both Mac and Windows, and you can convert data between the two platforms. (Members of this list will hardly be surprised to learn that it's the _Mac_ software that's clever enough to do the interconversion). You can also save data as a text file, which would have obviated the present IEEE conversion problem altogether. And, yes, I work part time for ADInstruments.
[End of plug]
|