信号NaN的有用性?
我最近在IEEE 754和x87架构上读了很多。 我正在考虑在我正在使用的一些数字计算代码中将NaN用作“缺失值”,并且我希望使用NaN信号发送将允许我在不希望出现的情况下捕获浮点异常继续“缺失的价值”。 相反,我会使用安静的NaN来让“缺失值”通过计算传播。 然而,信号NaN不能正常工作,因为我认为它们将基于存在于其上的(非常有限的)文档。
以下是我所知道的一个总结(所有这些使用x87和VC ++):
标准库提供了一种访问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寄存器中以将其存储到内存地址中)。
你可能会像我这样想:
但是,步骤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