我需要将一些额外信息打包到浮点NaN值中。我在Python中使用单精度IEEE 754浮点数(32位浮点数)。 Python和NumPy如何处理这些值?
理论
IEEE 754-2008标准似乎认为数字实际上不是数字,如果指数位(23..30)被设置,并且至少有一个有效位被设置。因此,如果我们将float转换为32位整数表示,则满足以下条件的任何内容都将成为:
i & 0x7f800000 == 0x7f800000
i & 0x007fffff != 0
这会让我有很多选择。但是,该标准似乎表示有效数字的最高位是 is_quiet ,应设置为避免计算中的异常。
实践测试
Python 2.7
为了确定,我运行了一些有趣结果的测试:
import math
import struct
std_nan = struct.unpack("f4", struct.pack("I", 0x7fc00000))[0]
spec_nan = struct.unpack("f4", struct.pack("I", 0x7f800001))[0]
spec2_nan = struct.unpack("f4", struct.pack("I", 0x7fc00001))[0]
print "{:08x}".format(struct.unpack("I", struct.pack("f4", std_nan))[0])
print "{:08x}".format(struct.unpack("I", struct.pack("f4", spec_nan))[0])
print "{:08x}".format(struct.unpack("I", struct.pack("f4", spec2_nan))[0])
这给出了:
7fc00000
7fc00001 <<< should be 7f800001
7fc00001
这个和一些进一步的测试似乎暗示某些东西(struct.unpack
?)总是设置 is_quiet 位。
NumPy的
我尝试使用NumPy,因为我总是可以依赖转换而不改变一个位:
import numpy as np
intarr = np.array([0x7f800001], dtype='uint32')
f = np.fromstring(intarr.tostring(), dtype='f4')
print np.isnan(f)
这给出了:
RuntimeWarning: invalid value encountered in isnan
[True]
但如果该值被0x7fc00001
替换,则没有错误。
假设
如果我设置 is_quiet 并使用剩余的比特用于我自己的目的,Python和NumPy都会很高兴。 Python自己处理这个位,NumPy依赖于低级语言实现和/或硬件FP实现。
问题
我的假设是否正确,是否可以通过某些官方文档证明或反驳?或者它是平台相关的东西之一?
我在这里发现了一些非常相关的内容:How to distinguish different types of NaN float in Python,但我找不到任何关于如何在Python或NumPy中处理带有额外信息的NaN的官方消息。
答案 0 :(得分:7)
在考虑了这段时间并看了一下源代码广告然后重新思考了一下之后,我想我可以回答我自己的问题。我的假设几乎是正确的,但不是整个故事。
由于NumPy和Python处理的数字完全不同,这个答案分为两部分。
使用NaNs在Python和NumPy中真正发生了什么
NumPy的
这可能略微针对特定于平台,但在大多数平台上,NumPy使用gcc
内置isnan
,后者反过来做得很快。在大多数情况下,运行时警告来自更深层次的硬件。 (NumPy可以使用几种确定NaN状态的方法,例如x!= x,至少可以在AMD 64平台上运行,但gcc
可以使用gcc
,这可能会使用double
一些很短的代码用于此目的。)
因此,从理论上讲,没有办法保证NumPy如何处理NaN,但实际上在更常见的平台上,它会像标准所说的那样,因为这是硬件的作用。 NumPy本身并不关心NaN类型。 (除了一些特定于NumPy的非hw支持的数据类型和平台。)
的Python
这里的故事变得有趣。如果平台支持IEEE浮点数(大多数都支持),Python会将C库用于浮点算术,因此在大多数情况下几乎都是硬件指令。所以NumPy应该没有任何区别。
除了......在Python中通常没有32位浮点数。 Python float对象使用C /* nantest.c - Test floating point nan behaviour with type casts */
#include <stdio.h>
#include <stdint.h>
static uint32_t u1 = 0x7fc00000;
static uint32_t u2 = 0x7f800001;
static uint32_t u3 = 0x7fc00001;
int main(void)
{
float f1, f2, f3;
float f1p, f2p, f3p;
double d1, d2, d3;
uint32_t u1p, u2p, u3p;
uint64_t l1, l2, l3;
// Convert uint32 -> float
f1 = *(float *)&u1; f2 = *(float *)&u2; f3 = *(float *)&u3;
// Convert float -> double (type cast, real conversion)
d1 = (double)f1; d2 = (double)f2; d3 = (double)f3;
// Convert the doubles into long ints
l1 = *(uint64_t *)&d1; l2 = *(uint64_t *)&d2; l3 = *(uint64_t *)&d3;
// Convert the doubles back to floats
f1p = (float)d1; f2p = (float)d2; f3p = (float)d3;
// Convert the floats back to uints
u1p = *(uint32_t *)&f1p; u2p = *(uint32_t *)&f2p; u3p = *(uint32_t *)&f3p;
printf("%f (%08x) -> %lf (%016llx) -> %f (%08x)\n", f1, u1, d1, l1, f1p, u1p);
printf("%f (%08x) -> %lf (%016llx) -> %f (%08x)\n", f2, u2, d2, l2, f2p, u2p);
printf("%f (%08x) -> %lf (%016llx) -> %f (%08x)\n", f3, u3, d3, l3, f3p, u3p);
return 0;
}
,这是一种64位格式。如何在这些格式之间转换特殊的NaN?为了了解在实践中发生了什么,以下小C代码有助于:
nan (7fc00000) -> nan (7ff8000000000000) -> nan (7fc00000)
nan (7f800001) -> nan (7ff8000020000000) -> nan (7fc00001)
nan (7fc00001) -> nan (7ff8000020000000) -> nan (7fc00001)
打印:
double
通过查看第2行,很明显我们有与Python相同的现象。因此,转换为{{1}}会在64位版本中的指数之后立即引入额外的 is_quiet 位。
这听起来有点奇怪,但实际上标准说(IEEE 754-2008,第6.2.3节):
将一个安静的NaN从较窄的格式转换为相同基数的更宽格式,然后再转换为相同的较窄格式,不应以任何方式改变安静的NaN有效载荷,除非使其成为规范。 / em>的
这没有说明发信号的NaN的传播。但是,第6.2.1节解释了这一点:
对于二进制格式,有效载荷在尾随有效数字段的p - 2个最低有效位中编码。
上面的 p 是精度的,32位浮点数是24位。所以,我的错误是使用已发信号的NaN进行有效载荷。
<强>摘要强>
我得到以下带回家的要点:
语言标准应该以受支持的格式提供可选的NaN转换为外部字符序列,这些字符序列将基本NaN字符序列附加到可以表示NaN有效负载的后缀(参见6.2)。有效负载后缀的形式和解释是语言定义的。语言标准应要求在将外部字符序列转换为支持格式时接受任何此类可选输出序列作为输入。