48

How to convert the following hex string to float (single precision 32-bit) in Python?

"41973333" -> 1.88999996185302734375E1

"41995C29" -> 1.91700000762939453125E1

"470FC614" -> 3.6806078125E4
mskfisher
  • 3,291
  • 4
  • 35
  • 48
jack
  • 17,261
  • 37
  • 100
  • 125

6 Answers6

81

In Python 3:

>>> import struct
>>> struct.unpack('!f', bytes.fromhex('41973333'))[0]
18.899999618530273
>>> struct.unpack('!f', bytes.fromhex('41995C29'))[0]
19.170000076293945
>>> struct.unpack('!f', bytes.fromhex('470FC614'))[0]
36806.078125

In Python 2:

>>> import struct
>>> struct.unpack('!f', '41973333'.decode('hex'))[0]
18.899999618530273
>>> struct.unpack('!f', '41995C29'.decode('hex'))[0]
19.170000076293945
>>> struct.unpack('!f', '470FC614'.decode('hex'))[0]
36806.078125
handras
  • 1,548
  • 14
  • 28
Denis Otkidach
  • 32,032
  • 8
  • 79
  • 100
  • 24
    In python3 you have to use `bytes.fromhex('41973333')` instead of `'41973333'.decode('hex')` – John La Rooy Oct 20 '09 at 05:56
  • `print(struct.unpack('!f', bytes.fromhex('D'))[0])` **ValueError: non-hexadecimal number found in fromhex() arg at position 1** any reason? – Varada Mar 31 '21 at 04:59
  • 1
    @VaradarajuG `'D'` is at position 0, and position 1 points to the end of string. Each hexadecimal encoded byte consist of 2 chars, so the length of your string must be even. Did you mean `bytes.fromhex('0D')` – Denis Otkidach Mar 31 '21 at 11:24
  • `struct.unpack('!f', bytes.fromhex('9f780441ccb646'))[0]` Out: `error: unpack requires a buffer of 4 bytes` – Benyamin Jafari May 23 '21 at 12:32
  • 1
    @BenyaminJafari The buffer’s size in bytes must match the size required by the format. So for your hex string, there are 7 bytes, which could be interpreted for example as a set of float, int and char. So if you do : `struct.unpack('!fhc', bytes.fromhex('9f780441ccb646'))[0]` , here f corresponds to float, h corresponds to int, and c corresponds to char. Then the output will be : `-5.251956147854424e-20` – Vandan Revanur Jun 01 '22 at 11:39
24

I recommend using the ctypes module which basically lets you work with low level data types. In your case you could say

from ctypes import *

def convert(s):
    i = int(s, 16)                   # convert from hex to a Python int
    cp = pointer(c_int(i))           # make this into a c integer
    fp = cast(cp, POINTER(c_float))  # cast the int pointer to a float pointer
    return fp.contents.value         # dereference the pointer, get the float

print convert("41973333")    # returns 1.88999996185302734375E1

print convert("41995C29")    # returns 1.91700000762939453125E1

print convert("470FC614")    # returns 3.6806078125E4

I believe that the ctypes module makes sense here, because you're essentially asking how to perform low-level bit casting. Your question is basically, how do I tell Python to take some data and interpret that data as if those exact same bits were a different data type?

In C if you had an int and wanted to interpret its bits as a float, you'd do roughly the same thing, taking a pointer and then casting and dereferencing it:

int i = 0x41973333;
float f = *((float*)&i);

and that's exactly what the Python code using the ctypes library is doing in my example.

Eli Courtwright
  • 186,300
  • 67
  • 213
  • 256
  • I used your method to convert a hex string to float and it returns a wrong value. `convert('9f91dd57' )` I get the output as : `-6.177606473100781e-20` However, when I use : `float.fromhex('9f91dd57')`, I get the right answer: `2677136727.0`. Could you please say why this is happening? – Vandan Revanur Jun 01 '22 at 12:20
6

I'm guessing this question relates to this one and you are working with 4 bytes rather than 8 hex digits.

"\x41\x91\x33\x33" is a 4 byte string even though it looks like 16

>>> len("\x41\x91\x33\x33")
4
>>> import struct  
>>> struct.unpack(">fff","\x41\x97\x33\x33\x41\x99\x5C\x29\x47\x0F\xC6\x14")
(18.899999618530273, 19.170000076293945, 36806.078125)

If you do need to deal with the string of hexdigits rather than the actual bytes, you can use struct.pack to convert it, like this

>>> for hx in ["41973333","41995C29","470FC614"]:
...     print(struct.unpack(">f",struct.pack(">i",int(hx,16)))[0])
... 
18.8999996185
19.1700000763
36806.078125
Community
  • 1
  • 1
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
4

Slice up the hex strings into 2-character chunks (bytes), make each chunk into the right byte with int formatting, struct.unpack when done. I.e.:

import struct 

testcases = {
"41973333": 1.88999996185302734375E1,
"41995C29": 1.91700000762939453125E1,
"470FC614": 3.6806078125E4,
}

def hex2float(s):
    bins = ''.join(chr(int(s[x:x+2], 16)) for x in range(0, len(s), 2))
    return struct.unpack('>f', bins)[0]

for s in testcases:
  print hex2float(s), testcases[s]

emitting, as desired:

18.8999996185 18.8999996185
19.1700000763 19.1700000763
36806.078125 36806.078125
Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
1

When working from string and without having to slice or index values.

import struct
...
q = int('0x425c0000',16)
b8 = struct.pack('i', q)
dec, = struct.unpack('f', b8)
-1

Gentelmen... Behold:

    class fl:
        def __init__(this, value=0, byte_size=4):

            this.value = value

            if this.value: # speedy check (before performing any calculations)
                Fe=((byte_size*8)-1)//(byte_size+1)+(byte_size>2)*byte_size//2+(byte_size==3)
                Fm,Fb,Fie=(((byte_size*8)-(1+Fe)), ~(~0<<Fe-1), (1<<Fe)-1)

                FS,FE,FM=((this.value>>((byte_size*8)-1))&1,(this.value>>Fm)&Fie,this.value&~(~0 << Fm))
                if FE == Fie: this.value=(float('NaN') if FM!=0 else (float('+inf') if FS else float('-inf')))
                else: this.value=((pow(-1,FS)*(2**(FE-Fb-Fm)*((1<<Fm)+FM))) if FE else pow(-1,FS)*(2**(1-Fb-Fm)*FM))

                del Fe; del Fm; del Fb; del Fie; del FS; del FE; del FM

            else: this.value = 0.0

    print fl( 0x41973333 ).value # >>> 18.899999618530273
    print fl( 0x41995C29 ).value # >>> 19.170000076293945
    print fl( 0x470FC614 ).value # >>> 36806.078125
    print fl( 0x00800000 ).value # >>> 1.1754943508222875e-38 (minimum float value)
    print fl( 0x7F7FFFFF ).value # >>> 340282346638528859811704183484516925440L (maximum float value)
    # looks like I've found a small bug o.o
    # the code still works though (the numbers are properly formatted)
    # the result SHOULD be: 3.4028234663852886e+38 (rounded)
    print fl( 0x3f80000000, 5 ).value # >>> 1.0

sorry for the little ".value" at the end...
this code has been used as a class in my program for nearly 2 years now.
(with a little editing, you can easily make it into a function)

credit to PyTony over at DaniWeb for the code.

unlike non-dynamic computing,
the code is not hard-wired to a fixed float size,
and works with any byte-size.

though I guess we still have a few bugs to work out. XDD
(I'll edit this code later (if I can) with the update)

all is good though for now though...
I havn't had a problem converting 3D game models with it. :)

Tcll
  • 7,140
  • 1
  • 20
  • 23