Alan Ennis
Alan Ennis

Reputation: 145

python 32 bit float conversion

Python 2.6 on Redhat 6.3

I have a device that saves 32 bit floating point value across 2 memory registers, split into most significant word and least significant word. I need to convert this to a float. I have been using the following code found on SO and it is similar to code I have seen elsewhere

#!/usr/bin/env python
import sys
from ctypes import *
first  = sys.argv[1]
second = sys.argv[2]

reading_1 = str(hex(int(first)).lstrip("0x"))
reading_2 = str(hex(int(second)).lstrip("0x"))

sample = reading_1 + reading_2

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(sample)

an example of the register values would be ;

register-1;16282 register-2;60597

this produces the resulting float of

1.21034872532

A perfectly cromulent number, however sometimes the memory values are something like;

register-1;16282 register-2;1147

which, using this function results in a float of;

1.46726675314e-36

which is a fantastically small number and not a number that seems to be correct. This device should be producing readings around the 1.2, 1.3 range.

What I am trying to work out is if the device is throwing bogus values or whether the values I am getting are correct but the function I am using is not properly able to convert them.

Also is there a better way to do this, like with numpy or something of that nature? I will hold my hand up and say that I have just copied this code from examples on line and I have very little understanding of how it works, however it seemed to work in the test cases that I had available to me at the time.

Thank you.

Upvotes: 1

Views: 9226

Answers (2)

abarnert
abarnert

Reputation: 365617

The way you've converting the two ints makes implicit assumptions about endianness that I believe are wrong.

So, let's back up a step. You know that the first argument is the most significant word, and the second is the least significant word. So, rather than try to figure out how to combine them into a hex string in the appropriate way, let's just do this:

import struct
import sys

first  = sys.argv[1]
second = sys.argv[2]

sample = int(first) << 16 | int(second)

Now we can just convert like this:

def convert(i):
    s = struct.pack('=i', i)
    return struct.unpack('=f', s)[0]

And if I try it on your inputs:

$ python floatify.py 16282 60597
1.21034872532
$ python floatify.py 16282 1147
1.20326173306

Upvotes: 1

Anders Johansson
Anders Johansson

Reputation: 4006

If you have the raw bytes (e.g. read from memory, from file, over the network, ...) you can use struct for this:

>>> import struct
>>> struct.unpack('>f', '\x3f\x9a\xec\xb5')[0]
1.2103487253189087

Here, \x3f\x9a\xec\xb5 are your input registers, 16282 (hex 0x3f9a) and 60597 (hex 0xecb5) expressed as bytes in a string. The > is the byte order mark.

So depending how you get the register values, you may be able to use this method (e.g. by converting your input integers to byte strings). You can use struct for this, too; this is your second example:

>>> raw = struct.pack('>HH', 16282, 1147)    # from two unsigned shorts
>>> struct.unpack('>f', raw)[0]              # to one float
1.2032617330551147

Upvotes: 7

Related Questions