信号NaN的有用性?

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

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

  • _EM_INVALID(IEEE“无效”exception)在遇到NaN时控制x87的行为
  • 如果_EM_INVALID被屏蔽(exception被禁用),则不会产生exception,操作可以返回安静的NaN。 涉及NaN信号的操作不会引发exception,但会被转换为安静的NaN。
  • 如果_EM_INVALID未被屏蔽(例外启用),一个无效的操作(例如,sqrt(-1))会导致一个无效的exception被抛出。
  • x87 永远不会产生信号NaN。
  • 如果_EM_INVALID未被屏蔽, 任何使用信号NaN(甚至用它初始化一个variables)都会导致一个无效的exception被抛出。

标准库提供了一种访问NaN值的方法:

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

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

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

如果_EM_INVALID 没有被屏蔽(exception被启用),那么甚至不能用信号发送一个variablesNaN: double dVal = std::numeric_limits<double>::signaling_NaN(); 因为这会引发一个exception(信号NaN值被加载到一个x87寄存器中以将其存储到存储器地址中)。

你可能会像我这样想:

  1. 掩码_EM_INVALID。
  2. 用信号NaN初始化variables。
  3. Unmask_EM_INVALID。

但是,步骤2会导致信号NaN转换为安静的NaN,所以后续使用它不会引发exception! 那么WTF?

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

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


编辑:

为了进一步说明我希望做的事情,下面是一个例子:

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

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

 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函数(或任何其他math运算)可能会掩盖这种检查,有些情况下,操作是最小的(也许只是一个补充),检查是昂贵的。 更不要说“缺失的价值”将合法的投入价值放在一边,如果一个计算合理地达到了这个价值(可能不大可能),那么这个价值就会产生错误。 在技​​术上也是正确的,用户input的数据应该根据该值进行检查,并采取适当的行动。 我觉得这个解决scheme不够优雅,性能也不够理想。 这是对性能至关重要的代码,我们绝对没有奢侈的并行数据结构或某种数据元素对象。

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; } } 

现在明确的检查被消除,性能应该得到改善。 我认为这将是所有的工作,如果我可以初始化vector而不接触FPU寄存器…

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

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

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

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

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

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

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

难道你不能只有一个const uint64_t位已被设置为那些信号南? 只要你把它当作一个整数types,信号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