【问题标题】:Are floating point operations resulting in infinity undefined behavior for IEC 559/IEEE 754 floating-point types浮点运算是否会导致 IEC 559/IEEE 754 浮点类型的无限未定义行为
【发布时间】:2019-09-29 13:32:02
【问题描述】:

我正在阅读Infinity not constexpr,这似乎表明创建无穷大是未定义的行为:

[expr]/4:

如果在计算表达式期间,结果未在数学上定义或不在其类型的可表示值范围内,则行为未定义。

不过,如果std::numeric_limits::is_iec559 等于真,那似乎给了我们更多的保证。

下面的代码利用这个保证来创建一个无限数。在constexpr 上下文中执行时,会导致编译器失败,因为这是undefined behavior,以防is_iec559 等于false。

// clang++ -std=c++17 -O3
#include <limits>

constexpr double createInfinity()
{
    static_assert(std::numeric_limits<double>::is_iec559, "asdf");
    double d = 999999999999;
    while (d != std::numeric_limits<double>::infinity())
    {
        d *= d;
    }
    return -1*d;
}

static_assert(createInfinity() == std::numeric_limits<double>::infinity(), "inf");

Code at Compiler Explorer

由于此函数总是导致无穷大,因此永远不能在有效的 C++ 程序中调用它。然而,正如我们在is_iec559 上断言的那样,我们得到了额外的保证。这个程序还无效吗?

  • 如果无效?拥有is_iec559 有什么意义?
  • 如果有效?为什么它在运行时有效而不在 constexpr 上下文中有效?

(答案可以同时使用C++17作为即将推出的C++20,请明确指出使用哪个)

【问题讨论】:

  • 为什么它在运行时有效,而不是在 constexpr 上下文中?”谁说它不是?
  • “它似乎给了我们更多的保证”。有什么保证?
  • 不幸的是,浮点在 C 和 C++ 标准中是一团糟。诸如“数学定义”和“可表示值的范围”之类的短语在 C++ 中从未定义过,尽管 C 说具有无穷大使得后者包含所有实数。但这并没有说明无穷大自身是否在范围内(例如,对于inf+0),并且无论如何都不是C++的规范。
  • 现在我们知道有两个浮点世界:IEC 559 世界和asdf 世界。

标签: c++ floating-point language-lawyer c++17 c++20


【解决方案1】:

等待一些时间有时会有所帮助,看起来 Clang 已经收到了一个可以编译此代码的补丁:https://reviews.llvm.org/D63793

在 r329065 之前,我们使用 [-max, max] 作为可表示的范围 值,因为 LLVM 的 fptrunc 不保证定义的行为时 从较大的浮点类型截断为较小的浮点类型。现在 已修复,我们可以让 clang 在此遵循正常的 IEEE 754 语义 将较大的范围 [-inf, +inf] 视为可表示的范围 价值观。

要注意的有趣元素(该修订中的代码 cmets 的一部分)是导致 NaN 的操作(尚未)被允许:

// [expr.pre]p4:
//   If during the evaluation of an expression, the result is not
//   mathematically defined [...], the behavior is undefined.
// FIXME: C++ rules require us to not conform to IEEE 754 here.

Example at compiler explorer:

#include <limits>

constexpr double createNan()
{
    static_assert(std::numeric_limits<double>::is_iec559, "asdf");
    double d = std::numeric_limits<double>::infinity() / std::numeric_limits<double>::infinity();
    return -1*d;
}

static_assert(createNan() != 0., "NaN");

【讨论】:

  • 看到这些标准相互矛盾会很有趣。混乱。
【解决方案2】:

程序格式错误。

[expr.const]/4

表达式e 是核心常量表达式,除非求值 的e,遵循抽象机器的规则,将评估 以下表达式之一:

  • [...]

  • 具有未定义行为的操作,如 [intro]通过 [cpp] 本文档 [ 注意: 包括,例如,有符号整数溢出 ([expr.prop]),某个指针 算术 ([expr.add]),除法 由零或某些移位操作—— 尾注 ];

  • [...]

[expr.pre]/4 说:

如果在计算表达式期间,结果未在数学上定义或不在可表示的值范围内 其类型,行为未定义。 [ 注意: 除以零,使用零除数形成余数,并且所有 浮点异常因机器而异,有时 可通过库函数进行调整。 — 尾注 ]

请注意,从数学上讲,两个有限数的乘积永远不会是无穷大,因此该乘积会触发未定义的行为。实现可以指定定义的表达式,但从标准的角度来看,它仍然是未定义的行为。实施的定义不追溯适用于标准的其他部分。因此,该程序是非良构的,因为它试图将触发未定义行为的表达式作为常量表达式求值。


有趣的是,numeric_limits&lt;double&gt;::infinity()constexpr。这可以。每[numeric.limits]/infinity

static constexpr T infinity() noexcept;

表示正无穷大,如果有的话。

has_­infinity != false 的所有专业都有意义。 在 is_­iec559 != false 的专业中是必需的。

如果is_iec559 == true,那么has_infinity == true,则返回无穷大值。如果is_iec559 == falsehas_infinity可能是true,此时也返回无穷大值,也可能是false,此时infinity()返回0。 (!)

但是,由于两个大数的乘积不是自动无穷大(而是未定义的行为),因此不存在矛盾。传递无穷大是可以的(无穷大值总是在可表示值的范围内),但是将两个大数相乘并假设结果是无穷大则不行。

【讨论】:

  • “纯粹未定义的行为”从何而来?
  • 据我了解,您的回答仍然包含无效的内容:根据引用,createInfinity 肯定不是核心常量表达式。前两段的写法还是有的,说明可能是CCE。
  • @T.C.我很抱歉。我在做白日梦。我已经重写了答案。
  • 对我来说似乎没问题 :) 我唯一可以争论的是无穷大是否是范围的一部分。从某种意义上说,它是范围的一部分,与iec559一样,浮点数可以表示无穷大。至少,如果我对这个决定负责,我肯定会以 OP 的程序格式正确的方式编写标准。
  • @LF:目前的部分(可悲的是不完整的)想法是生成一个非有限值不是一个常量表达式,但明确要求一个很好.
猜你喜欢
  • 2015-04-21
  • 1970-01-01
  • 1970-01-01
  • 2012-01-06
  • 2014-10-18
  • 1970-01-01
  • 1970-01-01
  • 2013-07-24
  • 2014-07-17
相关资源
最近更新 更多