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

Made with FB

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.

Osamu Shigematsu


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.

Robert Purves

'------------- 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.

Osamu Shigematsu


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.

Robert Purves


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....

Mark Chappell


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]

Robert