将无符号整数转换为 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]

如有错误请指正

ldexpfrexp 正数分解。

如果您可以接受最多 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.

最后的话

不要在生产中这样做。

  1. 您永远不必显式操作数字的机器表示。
  2. 即使出于某些不敬虔的原因您需要这样做,您也不应该做这种骇人听闻的事情。

这只是一个看起来很有趣的聪明的 float-hack。仅此而已。