Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert 4 byte IEEE (little endian) float binary representation to float

I am decoding a binary file, which has decimal numbers represented by four bytes, little endian. For example, 94 53 F0 40 represents 7.510202. Unfortunately, Python is giving me 7.51020240784.

When I try to parse this data using unpack("<f",sampledata)[0] I don't get exact representations of the original, due to the way Python stores values (for more information, see http://bugs.python.org/issue4114).

Unfortunately, I do need to get the exact same representation- regardless of discussions about the inaccuray of floats, because I need to write these values to a text file, with the same number of decimal places as they were initially written to the binary file with.

I'd rather stick to Python if possible, but am happy to implement a solution in C if necessary. The reason I cannot simply truncate the return of the unpack function, is that I cannot guarantee how many decimal places the original float had, for example 0C 02 0F 41 represents 8.938 according to my hex editor, from the original binary file, which only has 3 decimal places.

To be clear, I need to take four hex bytes as my input, and output either a text/ASCII or number representation of the IEEE 32-bit floating point number, that has the same number of decimal places as was intended by the creator of the file. The output I will use to create a CSV of the original binary data file, not for actually performing any calculations.

Any suggestions?

Example:

from __future__ import print_function
from struct import *

print("Should print 7.510202")

hexbytes = b"\x94\x53\xF0\x40"

# 01101001 11001000 11110001 01000000
# should print 7.510202

print(unpack("<f",hexbytes)[0])
like image 307
Chris Avatar asked Jul 10 '14 16:07

Chris


1 Answers

A 4-byte IEEE format floating point number holds approximately 7 digits. What you want to do is round the result of unpack to a total of 7 digits. From there the normal Python conversion from float to string will hide all the floating point nastiness from you.

def magnitude(x):
    return 0 if x==0 else int(math.floor(math.log10(abs(x)))) + 1

def round_total_digits(x, digits=7):
    return round(x, digits - magnitude(x))

>>> round_total_digits(struct.unpack('<f', '\x94\x53\xF0\x40')[0])
7.510202
>>> round_total_digits(struct.unpack('<f', '\x0C\x02\x0F\x41')[0])
8.938
>>> x = struct.unpack('<f', struct.pack('<f', 12345.67))[0]
>>> x
12345.669921875
>>> round_total_digits(x)
12345.67

Note that if your numbers did not originate from a direct conversion of a decimal number but were the result of a calculation, this could reduce the total accuracy. But not by much.

like image 101
Mark Ransom Avatar answered Nov 04 '22 05:11

Mark Ransom