将无符号整数转换为 python 中的浮点数
convert unsigned integer to float in python
我编写了一个从某些设备读取数据的套接字服务器。读取数据后,对字节应用二进制移位。之后我得到一个整数值,例如 1108304047
,我想将这个数字转换为 IEEE 754 浮点数 35.844417572021484。我找到了一些 struct.unpack 的解决方案,但在我看来这并不合理。首先我们将数字转换为字符串,然后再转换为浮点数。
有没有像Java中的Float.intBitsToFloat(1108304047)
这样的捷径。
我用 struct.unpack 找到的解决方案很长。它包含字符串转换、子字符串获取、零填充等。
def convert_to_float(value):
return struct.unpack("!f", hex(value)[2:].zfill(8).decode('hex'))[0]
正如您在 Java 中看到的那样,他们正在使用结构来达到目的。
/*
* Find the float corresponding to a given bit pattern
*/
JNIEXPORT jfloat JNICALL
Java_java_lang_Float_intBitsToFloat(JNIEnv *env, jclass unused, jint v)
{
union {
int i;
float f;
} u;
u.i = (long)v;
return (jfloat)u.f;
}
在 Python 中无法这样做,因此您需要使用 struct
library
This module performs conversions between Python values and C structs represented as Python strings
首先将数字转换为long
的表示
packed_v = struct.pack('>l', b)
然后解压到float
f = struct.unpack('>f', packed_v)[0]
这与 Java 中的类似。
def intBitsToFloat(b):
s = struct.pack('>l', b)
return struct.unpack('>f', s)[0]
如有错误请指正
ldexp
和 frexp
正数分解。
如果您可以接受最多 2-16 的相对误差,您可以仅使用基本算术和 ld/frexp
分解来表达转换的两边.
请注意,这比 struct hack 慢得多,后者可以更简洁地表示为 struct.unpack('f', struct.pack('I', value))
。
下面是分解方法
def to_bits(x):
man, exp = math.frexp(x)
return int((2 * man + (exp + 125)) * 0x800000)
def from_bits(y):
y -= 0x3e800000
return math.ldexp(
float(0x800000 + y & 0x7fffff) / 0x1000000,
(y - 0x800000) >> 23)
虽然from_bits
函数看起来比较吓人,但其实无非是to_bits
的反函数,修改后只进行一次浮点除法(不是速度的考虑,只是因为当我们确实需要使用浮点数的机器表示时,这应该是我们所拥有的那种心态)。因此,我将重点解释前向转换。
推导
回想一下,(正)IEEE 754 浮点数表示为偏置指数及其尾数的元组。低 23 位 m 是尾数,高 8 位 e (减去最高有效位,我们假设它始终为零)代表指数,因此
x = (1 + m / 223) * 2e - 127
设man' = m / 223 and exp ' = e - 127,然后 0 <= man' < 1 和 exp' 是一个整数。因此
(man' + exp' + 127) * 223
给出 IEEE 754 表示。
另一方面,frexp
分解计算一对 man, exp = frexp(x)
使得 man * 2 exp = x,且 0.5 <= man < 1.
稍加思索就会发现man' = 2 * man - 1 and exp' = exp - 1,因此它的 IEEE 机器表示是
(man' + exp' + 127) * 0x800000 = (2 * man + exp + 125) * 0x800000
错误分析
我们期望有多少舍入误差?好吧,让我们假设 frexp
在其分解中没有引入错误。不幸的是,这是不可能的,但我们可以放宽这一点。
主要特点是计算2 * man + (exp + 125)
。为什么? 0x800000
是 2 的完美幂,因此 2 的幂的浮点乘法几乎总是无损的(除非我们溢出),因为 FPU 只是将 23 << 23
添加到它的机器表示中(没有触及尾数,这是错误出现的时候)。同样,乘法 2 * man
也是无损的(类似于仅将 1 << 23
添加到机器表示)。此外,exp
和 125 是整数,因此 (exp + 125)
的计算精度也很高。
因此,我们留待分析m + e
的错误行为,其中1 <= m
< 1且|e| < 127。在最坏的情况下,m
已填充所有 23 位(对应于 m = 2 - 2-22)和 e
= +/- 127 . 在这里,不幸的是,这个加法会破坏 m
的 8 个最低有效位,因为它必须重新归一化 m
(它在 20 的指数范围内) 到 28 的指数范围,这意味着丢失 8 位。然而,由于尾数有 24 个有效位,我们实际上损失了 2-(24 - 8) 的精度,这是误差的上限。
在 from_bits
的类似推理中,您可以证明 float(0x800000 + y & 0x7fffff)
基本上是在计算操作 (1.0f + m),其中 m
最多可能有 23位精度,它严格小于 1。因此,我们将 20 范围内的精确数字与 2- 范围内的另一个数字相加1,所以我们预计会丢失一位。这表明我们在向后转换中会产生高达 2-22 的相对误差。
这两种转换都产生很小的舍入,如果你在 to_bits
中加入一个额外的乘法,你也可以将它的误差降低到 2-22.
最后的话
不要在生产中这样做。
- 您永远不必显式操作数字的机器表示。
- 即使出于某些不敬虔的原因您需要这样做,您也不应该做这种骇人听闻的事情。
这只是一个看起来很有趣的聪明的 float-hack。仅此而已。
我编写了一个从某些设备读取数据的套接字服务器。读取数据后,对字节应用二进制移位。之后我得到一个整数值,例如 1108304047
,我想将这个数字转换为 IEEE 754 浮点数 35.844417572021484。我找到了一些 struct.unpack 的解决方案,但在我看来这并不合理。首先我们将数字转换为字符串,然后再转换为浮点数。
有没有像Java中的Float.intBitsToFloat(1108304047)
这样的捷径。
我用 struct.unpack 找到的解决方案很长。它包含字符串转换、子字符串获取、零填充等。
def convert_to_float(value):
return struct.unpack("!f", hex(value)[2:].zfill(8).decode('hex'))[0]
正如您在 Java 中看到的那样,他们正在使用结构来达到目的。
/*
* Find the float corresponding to a given bit pattern
*/
JNIEXPORT jfloat JNICALL
Java_java_lang_Float_intBitsToFloat(JNIEnv *env, jclass unused, jint v)
{
union {
int i;
float f;
} u;
u.i = (long)v;
return (jfloat)u.f;
}
在 Python 中无法这样做,因此您需要使用 struct
library
This module performs conversions between Python values and C structs represented as Python strings
首先将数字转换为long
packed_v = struct.pack('>l', b)
然后解压到float
f = struct.unpack('>f', packed_v)[0]
这与 Java 中的类似。
def intBitsToFloat(b):
s = struct.pack('>l', b)
return struct.unpack('>f', s)[0]
如有错误请指正
ldexp
和 frexp
正数分解。
如果您可以接受最多 2-16 的相对误差,您可以仅使用基本算术和 ld/frexp
分解来表达转换的两边.
请注意,这比 struct hack 慢得多,后者可以更简洁地表示为 struct.unpack('f', struct.pack('I', value))
。
下面是分解方法
def to_bits(x):
man, exp = math.frexp(x)
return int((2 * man + (exp + 125)) * 0x800000)
def from_bits(y):
y -= 0x3e800000
return math.ldexp(
float(0x800000 + y & 0x7fffff) / 0x1000000,
(y - 0x800000) >> 23)
虽然from_bits
函数看起来比较吓人,但其实无非是to_bits
的反函数,修改后只进行一次浮点除法(不是速度的考虑,只是因为当我们确实需要使用浮点数的机器表示时,这应该是我们所拥有的那种心态)。因此,我将重点解释前向转换。
推导
回想一下,(正)IEEE 754 浮点数表示为偏置指数及其尾数的元组。低 23 位 m 是尾数,高 8 位 e (减去最高有效位,我们假设它始终为零)代表指数,因此
x = (1 + m / 223) * 2e - 127
设man' = m / 223 and exp ' = e - 127,然后 0 <= man' < 1 和 exp' 是一个整数。因此
(man' + exp' + 127) * 223
给出 IEEE 754 表示。
另一方面,frexp
分解计算一对 man, exp = frexp(x)
使得 man * 2 exp = x,且 0.5 <= man < 1.
稍加思索就会发现man' = 2 * man - 1 and exp' = exp - 1,因此它的 IEEE 机器表示是
(man' + exp' + 127) * 0x800000 = (2 * man + exp + 125) * 0x800000
错误分析
我们期望有多少舍入误差?好吧,让我们假设 frexp
在其分解中没有引入错误。不幸的是,这是不可能的,但我们可以放宽这一点。
主要特点是计算2 * man + (exp + 125)
。为什么? 0x800000
是 2 的完美幂,因此 2 的幂的浮点乘法几乎总是无损的(除非我们溢出),因为 FPU 只是将 23 << 23
添加到它的机器表示中(没有触及尾数,这是错误出现的时候)。同样,乘法 2 * man
也是无损的(类似于仅将 1 << 23
添加到机器表示)。此外,exp
和 125 是整数,因此 (exp + 125)
的计算精度也很高。
因此,我们留待分析m + e
的错误行为,其中1 <= m
< 1且|e| < 127。在最坏的情况下,m
已填充所有 23 位(对应于 m = 2 - 2-22)和 e
= +/- 127 . 在这里,不幸的是,这个加法会破坏 m
的 8 个最低有效位,因为它必须重新归一化 m
(它在 20 的指数范围内) 到 28 的指数范围,这意味着丢失 8 位。然而,由于尾数有 24 个有效位,我们实际上损失了 2-(24 - 8) 的精度,这是误差的上限。
在 from_bits
的类似推理中,您可以证明 float(0x800000 + y & 0x7fffff)
基本上是在计算操作 (1.0f + m),其中 m
最多可能有 23位精度,它严格小于 1。因此,我们将 20 范围内的精确数字与 2- 范围内的另一个数字相加1,所以我们预计会丢失一位。这表明我们在向后转换中会产生高达 2-22 的相对误差。
这两种转换都产生很小的舍入,如果你在 to_bits
中加入一个额外的乘法,你也可以将它的误差降低到 2-22.
最后的话
不要在生产中这样做。
- 您永远不必显式操作数字的机器表示。
- 即使出于某些不敬虔的原因您需要这样做,您也不应该做这种骇人听闻的事情。
这只是一个看起来很有趣的聪明的 float-hack。仅此而已。