【问题标题】:convert unsigned integer to float in python在python中将无符号整数转换为浮点数
【发布时间】:2015-05-08 13:03:51
【问题描述】:

我编写了一个从某些设备读取数据的套接字服务器。读取数据后,对字节应用二进制移位。之后我得到一个整数值,例如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]

【问题讨论】:

  • “我用 struct.unpack 找到了一些解决方案,但在我看来并不合理” 为什么不呢?这就是您可以在一行中解决此问题的方法。
  • “首先我们将数字转换为字符串,然后转换为浮点数。” 好吧,从概念上讲,它与其说是一个字符串,不如说是一个字节序列(如果你'重新使用 Python 3)。这正是您的 Java sn-p 正在做的事情,只是拼写不同。
  • 我的意思是,他们都没有真正进行“字符串”转换。 Python 需要一个字节缓冲区,Python 中最简单的形式是(字节)字符串。 Python 的 struct 模块适用于大多数您可以使用 C 风格的 structunion(就像您在 Java 中找到的那样)。

标签: python ieee-754 floating-point-conversion


【解决方案1】:

正如您在 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

此模块执行 Python 值和表示为 Python 字符串的 C 结构之间的转换

首先将数字转换为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]

如果我错了,请纠正我。

【讨论】:

  • 这完全是我想要的。
  • 对于更长的数字,请尝试float64 = 4607182418800017408; print struct.unpack('d', struct.pack('Q', int(bin(float64), 0)))[0] -- 结果为1.0
【解决方案2】:

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 / 223exp' = e - 127,然后 0 man' exp' 是一个整数。因此

(man' + exp' + 127) * 223

给出 IEEE 754 表示。

另一方面,frexp 分解计算出一对 man, exp = frexp(x),使得 man * 2exp = x,并且0.5 人

稍加思考就会发现 man' = 2 * man - 1 和 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 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. 即使出于某些不虔诚的原因您需要这样做,您也不应该做这种 hacky 的事情。

这只是一个看起来很有趣的巧妙浮动破解。它的意义不止于此。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-23
    • 2014-02-20
    • 2014-01-13
    相关资源
    最近更新 更多