gpt4 book ai didi

c++ - 信号 NaN 的用处?

转载 作者:IT老高 更新时间:2023-10-28 14:01:34 30 4
gpt4 key购买 nike

我最近阅读了很多关于 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 初始化变量。
  • 取消屏蔽_EM_INVALID。

  • 然而,第 2 步导致信号 NaN 被转换为一个安静的 NaN,所以它的后续使用将 不是 导致抛出异常!所以WTF?!

    信号 NaN 有什么用途或目的吗?我知道最初的意图之一是用它初始化内存,以便可以捕获对未初始化浮点值的使用。

    有人可以告诉我这里是否遗漏了什么吗?

    编辑:

    为了进一步说明我希望做的事情,这里有一个例子:

    考虑对数据 vector ( double )执行数学运算。对于某些操作,我希望允许 vector 包含“缺失值”(假设这对应于电子表格列,例如,其中某些单元格没有值,但它们的存在很重要)。对于某些操作,我不想让 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函数(或任何其他数学运算)可能会掩盖此检查,在某些情况下,运算很少(可能只是一个加法)并且检查成本很高。更不用说“缺失值”使合法输入值失去作用的事实,如果计算合法地到达该值(尽管可能是不可能的),则可能会导致错误。同样在技术上是正确的,应根据该值检查用户输入数据,并应采取适当的措施。我发现此解决方案不优雅且性能不理想。这是性能关键代码,我们绝对没有并行数据结构或某种数据元素对象的奢侈。

    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 寄存器的情况下初始化 vector ,这一切都会起作用......

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

    最佳答案

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

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

    如果您使用 ASM 编写,这将不是问题。但是在 C 中,尤其是在 C++ 中,我认为您必须颠覆类型系统才能用 NaN 初始化变量。我建议使用 memcpy .

    关于c++ - 信号 NaN 的用处?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2247447/

    30 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com