自定义NaN的行为在Python和Numpy中浮动

DrV*_*DrV 11 python numpy python-2.7

我需要将一些额外的信息打包到浮点NaN值中.我在Python中使用单精度IEEE 754浮点数(32位浮点数).Python和NumPy如何处理这些值?

理论

如果指数位(23..30)被设置,并且至少有一个有效位被设置,则IEEE 754-2008标准似乎认为数字实际上不是数字.因此,如果我们将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])
Run Code Online (Sandbox Code Playgroud)

这给出了:

7fc00000
7fc00001 <<< should be 7f800001
7fc00001
Run Code Online (Sandbox Code Playgroud)

这个和一些进一步的测试似乎暗示某些东西(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)
Run Code Online (Sandbox Code Playgroud)

这给出了:

RuntimeWarning: invalid value encountered in isnan
[True]
Run Code Online (Sandbox Code Playgroud)

但如果值被替换0x7fc00001,则没有错误.

假设

Python和NumPy都会很高兴,如果我设置is_quiet并为了我自己的目的使用其余的位.Python自己处理这个位,NumPy依赖于低级语言实现和/或硬件FP实现.

我的假设是否正确,是否可以通过某些官方文件证明或反驳?或者它是平台依赖的东西之一?

我在这里找到了一些非常相关的东西:如何在Python中区分不同类型的NaN浮点数,但是我找不到任何关于如何在Python或NumPy中处理带有额外信息的NaN的官方文字.

DrV*_*DrV 7

在考虑了这段时间并查看源代码广告然后重新思考了一下之后,我想我可以回答我自己的问题.我的假设几乎是正确的,但不是整个故事.

由于NumPy和Python处理的数字完全不同,这个答案分为两部分.

使用NaNs在Python和NumPy中真正发生了什么

NumPy的

这可能是针对特定平台的,但在大多数平台上,NumPy使用gcc内置功能isnan,而内置功能可以快速完成某些任务.在大多数情况下,运行时警告来自更深层次的硬件.(NumPy可以使用几种确定NaN状态的方法,例如x!= x,它至少可以在AMD 64平台上运行,但是gcc它可以gcc使用一些相当短的代码.)

所以,从理论上讲,没有办法保证NumPy如何处理NaN,但实际上在更常见的平台上它会像标准所说的那样,因为这就是硬件的作用.NumPy本身并不关心NaN类型.(除了一些特定于NumPy的非hw支持的数据类型和平台.)

蟒蛇

这里的故事变得有趣.如果平台支持IEEE浮点数(大多数都支持),Python会将C库用于浮点算术,因此在大多数情况下几乎都是硬件指令.所以NumPy应该没有任何区别.

除了......在Python中通常没有32位浮点数这样的东西.Python float对象使用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;
    }
Run Code Online (Sandbox Code Playgroud)

这打印:

nan (7fc00000) -> nan (7ff8000000000000) -> nan (7fc00000)
nan (7f800001) -> nan (7ff8000020000000) -> nan (7fc00001)
nan (7fc00001) -> nan (7ff8000020000000) -> nan (7fc00001)
Run Code Online (Sandbox Code Playgroud)

通过查看第2行,我们可以看到与Python相同的现象.因此,转换为在64位版本中的指数之后立即double引入额外的is_quiet位.

这听起来有点奇怪,但实际上标准说(IEEE 754-2008,第6.2.3节):

在相同的基数中将安静的NaN从较窄的格式转换为更宽的格式,然后返回到相同的较窄格式,不应以任何方式改变安静的NaN有效载荷,除非使其成为规范.

这没有说明信号NaN的传播.但是,第6.2.1节对此进行了解释:

对于二进制格式,有效载荷在尾随有效数字段的p-2个最低有效位中编码.

上面的p是精度,32位浮点数是24位.所以,我的错误是使用已发信号的NaN进行有效负载.

摘要

我得到以下带回家点:

  • IEEE 754-2008支持和鼓励使用qNaNs(安静的NaN)
  • 奇怪的结果是因为我试图使用sNaNs和类型转换导致is_quiet位被设置
  • NumPy和Python在最常见的平台上根据IEEE 754行事
  • 实现很大程度上依赖于底层的C实现,因此保证很少(在Python中甚至有一些代码确认NaN不会像在某些平台上那样处理)
  • 解决这个问题的唯一安全方法是用有效载荷做一些DIY

但是,有一件事既没有在Python中也没有在NumPy中实现(也没有我遇到的任何其他语言).第5.12.1节:

语言标准应该以受支持的格式提供可选的NaN转换为外部字符序列,外部字符序列将基本NaN字符序列附加到可以表示NaN有效负载的后缀(参见6.2).有效负载后缀的形式和解释是语言定义的.语言标准应要求任何此类可选输出序列在外部字符序列转换为支持格式时被接受为输入.