我需要将一些额外的信息打包到浮点NaN值中。我在Python中使用单精度IEEE 754浮点数(32位浮点数)。Python和NumPy如何处理这些值?
理论
如果设置了指数位(23..30),并且设置了至少一个有效位,则IEEE 754-2008标准似乎认为数字实际上不是数字。因此,如果将浮点数转换为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实现。
题
我的假设是正确的,可以通过某些官方文件证明或否定吗?还是这些依赖平台的事情之一?
我在这里发现了一些很相关的东西:如何在Python中区分不同类型的NaN浮点数,但是我找不到任何关于在Python或NumPy中如何处理携带额外信息的NaN的正式说法。
考虑了一段时间之后,看了一下源代码广告,然后重新考虑了一下,我想我可以回答我自己的问题。我的假设几乎是正确的,但并非全部。
由于NumPy和Python处理数字的方式大不相同,因此此答案分为两部分。
使用NaN在Python和NumPy中真正发生了什么
NumPy
这可能是特定于平台的,但在大多数平台上,NumPy使用gcc
内置的isnan
,这反过来又可以快速地完成某些工作。在大多数情况下,运行时警告来自更深的层次,来自硬件。(NumPy可以使用多种确定NaN状态的方法,例如x!= x,它至少可以在AMD 64平台上运行,但是gcc
下降到gcc
,可能为此目的使用了一些很短的代码。)
因此,从理论上讲,没有办法保证NumPy如何处理NaN,但是在实践中,在更常见的平台上,它将按照标准的规定进行操作,因为这就是硬件的作用。NumPy本身根本不关心NaN类型。(除了某些特定于NumPy的,非硬件支持的数据类型和平台。)
蟒蛇
这里的故事变得有趣。如果平台支持IEEE浮点数(大多数情况下),则Python使用C库进行浮点算术运算,因此在大多数情况下几乎直接使用硬件指令。因此,NumPy应该没有任何区别。
除了...通常在Python中没有32位浮点数。Python浮点对象使用C double
,这是一种64位格式。如何在这些格式之间转换特殊的NaN?为了了解实际情况,下面的一些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;
}
打印:
nan (7fc00000) -> nan (7ff8000000000000) -> nan (7fc00000)
nan (7f800001) -> nan (7ff8000020000000) -> nan (7fc00001)
nan (7fc00001) -> nan (7ff8000020000000) -> nan (7fc00001)
通过查看第二行,很明显,我们具有与Python相同的现象。因此,在64位版本中,指数转换之后立即double
引入了额外的is_quiet位。
这听起来有些奇怪,但是实际上标准说(IEEE 754-2008,第6.2.3节):
将安静的NaN从较窄的格式转换为相同基数的较宽格式,然后再转换回相同的较窄的格式,除非使其成为规范,否则不应以任何方式更改安静的NaN有效载荷。
这没有说出信号NaN的传播。但是,这在6.2.1节中进行了解释:
对于二进制格式,有效载荷以尾随有效位的p-2个最低有效位进行编码。
上面的p是精度,对于32位浮点数为24位。因此,我的错误是使用信号通知的NaN作为有效载荷。
概要
我得到以下要点:
但是,有一件事情既没有用Python也没有用NumPy(也没有我遇到的任何其他语言)实现。第5.12.1节:
语言标准应提供将支持的格式的NaN可选转换为外部字符序列的功能,该功能将基本NaN字符序列的后缀附加到代表NaN有效载荷的后缀(请参见6.2)。有效负载后缀的形式和解释是语言定义的。语言标准应要求在将外部字符序列转换为支持的格式时,接受任何此类可选输出序列作为输入。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句