信号NaN的有用性?

我最近在IEEE 754和x87架构上读了很多。 我正在考虑在我正在使用的一些数字计算代码中将NaN用作“缺失值”,并且我希望使用NaN信号发送将允许我在不希望出现的情况下捕获浮点异常继续“缺失的价值”。 相反,我会使用安静的NaN来让“缺失值”通过计算传播。 然而,信号NaN不能正常工作,因为我认为它们将基于存在于其上的(非常有限的)文档。

以下是我所知道的一个总结(所有这些使用x87和VC ++):

  • _EM_INVALID(IEEE“无效”异常)在遇到NaN时控制x87的行为
  • 如果_EM_INVALID被屏蔽(异常被禁用),则不会产生异常,并且操作可以返回安静的NaN。 涉及NaN信号的操作不会引发异常,但会转换为安静的NaN。
  • 如果_EM_INVALID未被屏蔽(启用了异常),则无效操作(例如,sqrt(-1))会导致引发无效异常。
  • x87 永远不会产生信号NaN。
  • 如果_EM_INVALID未被屏蔽, 任何使用信号NaN(甚至用它初始化一个变量)都会导致无效的异常被抛出。
  • 标准库提供了一种访问NaN值的方法:

    std::numeric_limits<double>::signaling_NaN();
    

    std::numeric_limits<double>::quiet_NaN();
    

    问题在于我看不到任何信号NaN。 如果_EM_INVALID被屏蔽,它的行为与安静的NaN完全相同。 由于没有NaN与任何其他NaN相比,没有逻辑上的差异。

    如果_EM_INVALID未被屏蔽(异常已启用),那么甚至不能使用信号发送初始化变量NaN: double dVal = std::numeric_limits<double>::signaling_NaN(); 因为这会引发异常(将信号NaN值加载到x87寄存器中以将其存储到内存地址中)。

    你可能会像我这样想:

  • 掩码_EM_INVALID。
  • 用信号NaN初始化变量。
  • Unmask_EM_INVALID。
  • 但是,步骤2会将NaN信号转换为安静的NaN,因此后续使用它不会引发异常! 那么WTF?!

    信号NaN是否有任何用途或目的? 我明白其中一个原意是用它初始化内存,以便可以捕获单位浮点值的使用。

    有人可以告诉我,我是否在这里失去了一些东西?


    编辑:

    为了进一步说明我希望做什么,这里是一个例子:

    考虑对数据向量执行数学运算(双精度)。 对于某些操作,我想允许向量包含一个“缺失值”(假设这对应于电子表格列,例如,其中某些单元格没有值,但它们的存在是显着的)。 对于某些操作,我不想让矢量包含“缺失值”。 也许我想采取一个不同的行动方式,如果一个“缺失值”出现在集合中 - 也许执行一个不同的操作(因此这不是一个无效的状态)。

    这个原始代码看起来像这样:

    const double MISSING_VALUE = 1.3579246e123;
    using std::vector;
    
    vector<double> missingAllowed(1000000, MISSING_VALUE);
    vector<double> missingNotAllowed(1000000, MISSING_VALUE);
    
    // ... populate missingAllowed and missingNotAllowed with (user) data...
    
    for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) {
        if (*it != MISSING_VALUE) *it = sqrt(*it); // sqrt() could be any operation
    }
    
    for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) {
        if (*it != MISSING_VALUE) *it = sqrt(*it);
        else *it = 0;
    }
    

    请注意,必须在每次循环迭代中检查“缺失值”。 虽然我理解大多数情况下, sqrt函数(或任何其他数学运算)可能会掩盖此检查,但有些情况下操作很少(可能只是一个加法),并且检查代价高昂。 更不用说“缺失的价值”将合法的输入值取消,并且如果计算合理地达到该值(可能不大可能),则可能导致错误。 在技​​术上也是正确的,用户输入的数据应该根据该值进行检查,并采取适当的行动。 我觉得这个解决方案不够优雅,并且性能不够理想。 这是性能至关重要的代码,我们绝对没有奢侈的并行数据结构或某种数据元素对象。

    NaN版本将如下所示:

    using std::vector;
    
    vector<double> missingAllowed(1000000, std::numeric_limits<double>::quiet_NaN());
    vector<double> missingNotAllowed(1000000, std::numeric_limits<double>::signaling_NaN());
    
    // ... populate missingAllowed and missingNotAllowed with (user) data...
    
    for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) {
        *it = sqrt(*it); // if *it == QNaN then sqrt(*it) == QNaN
    }
    
    for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) {
        try {
            *it = sqrt(*it);
        } catch (FPInvalidException&) { // assuming _seh_translator set up
            *it = 0;
        }
    }
    

    现在明确的检查被消除并且应该改进性能。 我认为如果我可以在不接触FPU寄存器的情况下初始化矢量,这一切都可以工作。

    此外,我会想象任何自尊的sqrt实现检查NaN并立即返回NaN。


    据我所知,NaN信号发送的目的是初始化数据结构,但是当然,C中的运行时初始化会导致将NaN作为初始化的一部分加载到浮点寄存器中,从而触发信号,因为编译器不是不知道这个浮点值需要使用整数寄存器来复制。

    我希望你能用信号NaN初始化一个static值,但即使这样也需要编译器进行一些特殊的处理,以避免它转换为安静的NaN。 你也许可以使用一些魔法来避免在初始化时将它视为浮点值。

    如果你在ASM中编写,这不会是一个问题。 但在C中,特别是在C ++中,我认为你将不得不颠覆类型系统以便用NaN初始化一个变量。 我建议使用memcpy


    使用特殊的值(即使是NULL)会使数据变得更加混乱,代码变得更加混乱。 要区分QNaN结果和QNaN“特殊”值是不可能的。

    您可能会更好地维护并行数据结构来跟踪有效性,或者可能让您的FP数据处于不同(稀疏)的数据结构中,以仅保留有效数据。

    这是相当一般的建议; 在某些情况下,特殊值非常有用(例如,内存或性能限制非常严格),但随着环境变得越来越大,它们可能会导致比它们的价值更大的困难。


    难道你不能只有一个const uint64_t的位已被设置为那些信号nan? 只要你把它当作一个整数类型,信号nan与其他整数并没有什么不同。 你可以通过指针投射将它写在你想要的地方:

    Const uint64_t sNan = 0xfff0000000000000;
    Double[] myData;
    ...
    Uint64* copier = (uint64_t*) &myData[index];
    *copier=sNan | myErrorFlags;
    

    有关要设置的位的信息:https://www.doc.ic.ac.uk/~eedwards/compsys/float/nan.html

    链接地址: http://www.djcxy.com/p/58491.html

    上一篇: Usefulness of signaling NaN?

    下一篇: What are the other NaN values?