interpret 4 byte as 32-bit float (IEEE-754)

Bengt Richter bokr at oz.net
Sun Jan 16 00:55:22 EST 2005


On Sat, 15 Jan 2005 11:00:36 -0800, Scott David Daniels <Scott.Daniels at Acm.Org> wrote:

>G.Franzkowiak wrote:
>> Scott David Daniels schrieb:
>> 
>>> franzkowiak wrote:
>>>
>>>> I've read some bytes from a file and just now I can't interpret 4 
>>>> bytes in this dates like a real value.  An extract from my program:
>>>> def l32(c):
>>>>     return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) +  
>>>> (ord(c[3])<<24)
>>>> ...
>>>> value = l32(f.read(4))      <---  3F 8C CC CD  should be 1.11
>>>>
>>> OK, here's the skinny (I used blocks & views to get the answer):
>>>
>>> import struct
>>> bytes = ''.join(chr(int(txt, 16)) for txt in '3F 8C CC CD'.split())
>>> struct.unpack('>f', bytes)
>>>
>>> I was suspicious of that first byte, thought it might be an exponent,
>>> since it seemed to have too many on bits in a row to be part of 1.11.
>>>
>>> -Scott David Daniels
>>> Scott.Daniels at Acm.Org
>> 
>> 
>> Ok, I the string exist with "mystr = f.read(4)" and the solution for 
>> this case is in your line "struct.unpack('>f', bytes)"
>> But what can I do when I want the interpret the content from the Integer 
>> myInt  (*myInt = 0x3F8CCCCD) like 4-byte-real ?
>> This was stored with an othes system in a binary file to
>> CD CC 8C 3F and now is it in python in value. The conversion is not 
>> possible. It's right... one of this bytes is an exponent.
>> I want copy the memory content from the "value address" to "myReal 
>> address" and use print "%f" %myReal.
>> Is myReal then the right format ?
>> What can I do with python, in FORTH is it simple
>> ( >f f. )
>> 
>> gf
>> 
>> 
>> 
>If you really want to do this kind of byte fiddling:
>     http://members.dsl-only.net/~daniels/block.html
>
>Then:
>     from block import Block, View
>     b = Block(4) # enough space for one float (more is fine)
>     iv = View('i', b) # getting to it as an integer
>     fv = View('f', b) # same memory as floating point
>     iv[0] = 0x3F8CCCCD # Here is a sample just using the integer
>     print fv[0]
>
>On an Intel/Amd/Generic "PC" machine, you should get 1.1
>
Ok, for most bit patterns (except QNANs), you can do it in pure python:

----< i32as_single.py >------------------------------------
class NoQNAN(ValueError): pass  # use to flag inability to return QNANs
    
def i32as_single(i):
    """
    (UIAM in my interpretation of a 1995 Pentium Processor Developer's Manual)
    This converts bits in a 32-bit integer as if they were bits
    of a single-precision IEEE 754 floating point number to python float
    
    +---+---+---+---+---+---+---+---+---+---+---+---+ ... +---+---+---+---+
    | s | e   e   e   e   e   e   e   eb| b   b   b   ...  b   b   b   b0 |
    +---+---+---+---^---+---+---+---^---+---+---+---^ ... ^---+---+---+---+
      31  30  29  28  27  26  25  24  23  22  21  20        3   2   1   0
    
    where s is the sign bit, and is the only thing that changes between + and -
    and e..eb is an 8-bit biased (by 127) exponent, and eb is the hidden
    unit 1 bit followed by 23 b..b0 significant "fraction" bits",
    normalized so that the most significant bit is always 1 and therefore
    doesn't have to be stored at eb, except that when all but the sign bit
    are zero, eb is ignored and the value is then a signed zero value.
    The binary fraction starting bit is after the hidden '1' bit eb at 23,
    so viewing bits 0..23 as an integer, we have to divide by 2**23 (or
    adjust the exponent) to get the 1.xxxx values when the official unbiased
    exponent is zero (offset value 127).  Thus 0x3f800000 is zero unbiased
    exponent and no other bits, for a value of 1.0

    A biased exponent of 255 signifies a NaN, and a biased exponent of
    zero signifies a denormal (which doesn't have a hidden bit, and whose
    unit bit is bit 22). Single precision denormals can be normalized
    in python (double) float format, which is done for the return value.
    """
    signbit = i&0x80000000
    if not i&0x7fffffff: return signbit and -0.0 or 0.0 # if -0.0 available
    biased_exp = (i>>23) & 0xff # bits 30-23
    unitbit = biased_exp and 0x800000 or 0 # bit 23, or 0 for denormals
    significand = i & 0x7fffff  # bits 22-0
    if biased_exp == 255:
        if significand:
            raise NoQNAN, "Sorry, can't generate QNAN from %08x" % i
        return signbit and -1e9999 or 1e9999  # XXX s/b INF's for sure??
    adjexp = (biased_exp or 1) - 127 - 23 # adjusted for denormal
    posvalue = (significand + unitbit)*2.0**adjexp
    return signbit and -posvalue or posvalue

def test():
    import struct
    num = 0
    for sign in (0, 2*(-2**30)):
        for i3 in xrange(128):
            num3 = i3<<24
            for i2 in xrange(256):
                print '\r%08x'%(num,),  # show progress
                num2 = i2<<16
                # skip mid byte of significand, make 0
                # and twiddle only a few bits at the bottom
                for num0 in xrange(8):
                    num = sign+num3+num2+num0
                    s = ''.join(map(chr,(num0, 0, i2,((sign and 0x80 or 0)+i3))))
                    try: ti32as = i32as_single(num)
                    except NoQNAN: continue
                    tstruct = struct.unpack('f', s)[0] # XXX '<f' => no INF ??
                    if ti32as != tstruct:
                        print '\n%x =>%r\n%r => %r' % (num, ti32as, s, tstruct)
                    
if __name__ == '__main__':
    test()
-----------------------------------------------------------


[21:47] C:\pywk\clp>i32as_single.py
C:\pywk\clp\i32as_single.py:31: FutureWarning: hex/oct constants > sys.maxint will return positi
ve values in Python 2.4 and up
  signbit = i&0x80000000
7fff0007C:\pywk\clp\i32as_single.py:51: FutureWarning: %u/%o/%x/%X of negative int will return a
 signed string in Python 2.4 and up
  print '\r%08x'%(num,),  # show progress
ff7f0007C:\pywk\clp\i32as_single.py:38: FutureWarning: %u/%o/%x/%X of negative int will return a
 signed string in Python 2.4 and up
  raise NoQNAN, "Sorry, can't generate QNAN from %08x" % i
fffe0007

Of course, the test() gives a clue how you might write the whole thing using struct
by just summing the four chr-ed extracted bytes from the input i ;-)

Anyway, a couple things (how to make a QNAN in python without calling struct, and '<' in struct):

 >>> from i32as_single import i32as_single as i32f
 >>> i32f(0x3f8ccccd)
 1.1000000238418579
 >>> i32f(0x00000000)
 0.0
 >>> i32f(0x7f800000)
 1.#INF
 >>> i32f(0xff800000)
 -1.#INF

But I don't know how to build QNaNs:

 >>> i32f(0x7f800001)
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "i32as_single.py", line 38, in i32as_single
     raise NoQNAN, "Sorry, can't generate QNAN from %08x" % i
 i32as_single.NoQNAN: Sorry, can't generate QNAN from 7f800001

Whereas struct does:

 >>> import struct
 >>> struct.unpack('f','\x00\x00\x80\x7f')[0]
 1.#INF
 >>> struct.unpack('f','\x01\x00\x80\x7f')[0]
 1.#QNAN

BTW, your example:
 >>> struct.unpack('f','\xcd\xcc\x8c\x3f')[0]
 1.1000000238418579
 >>> i32f(0x3f8ccccd)
 1.1000000238418579

But what does this mean? (I wanted to test with little endian unpacking, since
that is what I knew I created, but that isn't what '<' means?? ...)

 >>> struct.unpack('f','\x00\x00\x80\x7f')[0]
 1.#INF
 >>> struct.unpack('<f','\x00\x00\x80\x7f')[0]
 3.4028236692093846e+038

Regards,
Bengt Richter



More information about the Python-list mailing list