如何将4字节IEEE(小端)浮点二进制表示转换为浮点数
我正在解码一个二进制文件,这个文件里的十进制数字是用四个字节表示的,采用小端格式。比如,94 53 F0 40
表示的是7.510202。不过,使用Python时,我得到的是7.51020240784。
当我尝试用unpack("<f",sampledata)[0]
来解析这些数据时,得到的结果并不是原始值的准确表示,这是因为Python存储数值的方式(想了解更多,可以查看http://bugs.python.org/issue4114)。
可惜的是,我确实需要得到完全相同的表示——不管关于浮点数不准确性的讨论,因为我需要将这些值写入一个文本文件,保留与它们最初在二进制文件中写入时相同的小数位数。
如果可能的话,我更愿意继续使用Python,但如果必要的话,我也乐意用C语言来实现解决方案。我不能简单地截断unpack函数的返回值,因为我无法保证原始浮点数有多少位小数。例如,0C 02 0F 41
在我的十六进制编辑器中表示8.938,而这个原始二进制文件只有3位小数。
为了明确,我需要将四个十六进制字节作为输入,输出一个文本/ASCII或数字表示的IEEE 32位浮点数,且小数位数要与文件创建者的意图一致。这个输出我将用来创建原始二进制数据文件的CSV,而不是用来进行任何计算。
有什么建议吗?
示例:
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])
3 个回答
这里有一个关于如何进行小端编码和解码的例子。这个例子没有解决任何四舍五入的问题,不过看起来上面的回答已经处理好了这些问题。
import csv, os
import struct
test_floats = [1.2, 0.377, 4.001, 5, -3.4]
## write test floats to a new csv file:
path_test_csv = os.path.abspath('data-test/test.csv')
print path_test_csv
test_csv = open(path_test_csv, 'w')
wr = csv.writer(test_csv)
for x in test_floats:
wr.writerow([x])
test_csv.close()
## write test floats as binary
path_test_binary = os.path.abspath('data-test/test.binary')
test_binary = open(path_test_binary, 'w')
for x in test_floats:
binary_data = struct.pack('<f', x)
test_binary.write(binary_data)
test_binary.close()
## read in test binary
binary = open(path_test_binary, 'rb')
binary.seek(0,2) ## seeks to the end of the file (needed for getting number of bytes)
num_bytes = binary.tell() ## how many bytes are in this file is stored as num_bytes
# print num_bytes
binary.seek(0) ## seeks back to beginning of file
i = 0 ## index of bytes we are on
while i < num_bytes:
binary_data = binary.read(4) ## reads in 4 bytes = 8 hex characters = 32-bits
i += 4 ## we seeked ahead 4 bytes by reading them, so now increment index i
unpacked = struct.unpack("<f", binary_data) ## <f denotes little endian float encoding
print tuple(unpacked)[0]
uint32_t b = 0x40F05394 + printf("");
printf("%.11f\n", *(float *) &b);
在我的(小端)系统中打印:
7.51020240784
所以你需要用 f
转换符打印更多的数字。和 Python 一样,你可以直接要求打印的数字位数。
举个例子:
print "%.11f" % (unpack("<f",hexbytes)[0])
如果你文本文件中要打印的数字位数是变化的,你也需要在文本文件中存储这个信息。
然后在 C 语言中你可以这样打印:
int p = 11;
printf("%.*f\n", p, *(float *) &b); // 11 here can be a variable
在 Python 中:
p = 11
print "%.*f" % (p, (unpack("<f",hexbytes)[0])) # 11 can be a variable
当然,要从 0x9453F040
得到 0x40F05394
,你只需要重新排列字节的顺序。
一个4字节的IEEE格式浮点数大约可以表示7位数字。你想要做的是把unpack
的结果四舍五入到总共7位数字。这样,正常的Python将浮点数转换成字符串时,就会把那些复杂的浮点数问题隐藏起来。
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
需要注意的是,如果你的数字不是直接从一个十进制数字转换过来的,而是通过计算得出的,这可能会降低整体的准确性。不过影响不大。