首页 > 解决方案 > std::min(0.0, 1.0) 和 std::max(0.0, 1.0) 会产生未定义的行为吗?

问题描述

这个问题很清楚。下面给出了我认为这些表达式可能会产生未定义行为的原因。我想知道我的推理是对还是错以及为什么。

短读

(IEEE 754)double不是Cpp17LessThanComparable因为<不是严格的弱排序关系,因为NaN. 因此,违反了和的Requires元素。std::min<double>std::max<double>

长读

所有参考都遵循n4800std::min和的规格std::max在 24.7.8 中给出:

template<class T> constexpr const T& min(const T& a, const T& b);
template<class T> constexpr const T& max(const T& a, const T& b);
要求:[...] 类型 T 应为Cpp17LessThanComparable(表 24)。

表 24 定义了Cpp17LessThanComparable并表示:

要求:<是严格的弱排序关系(24.7)

第 24.7/4 节定义了严格的弱排序。特别是,因为<它指出“如果我们定义equiv(a, b)!(a < b) && !(b < a)thenequiv(a, b) && equiv(b, c)意味着equiv(a, c)”。

现在,根据 IEEE 754 equiv(0.0, NaN) == trueequiv(NaN, 1.0) == trueequiv(0.0, 1.0) == false我们得出结论,这不是<严格的弱排序。因此, (IEEE 754)不是Cpp17LessThanComparable,这违反了andRequires子句。double std::minstd::max

最后,15.5.4.11/1 说:

违反函数的Requires:元素中指定的任何先决条件会导致未定义的行为 [...]。

更新1:

问题的重点不是争论那个std::min(0.0, 1.0)是未定义的,当程序评估这个表达式时,任何事情都可能发生。它返回0.0。时期。(我从来没有怀疑过。)

关键是要显示标准的(可能的)缺陷。在对精确度的值得称赞的追求中,该标准经常使用数学术语,弱严格排序只是一个例子。在这些情况下,数学精度和推理必须一路走好。

例如,请看 Wikipedia 对严格弱排序的定义。它包含四个要点,每个要点都以“For every x [...] in S...”开头。他们都没有说“对于 S 中对算法有意义的某些值 x”(什么算法?)。此外,规范中std::min明确表示“T应为Cpp17LessThanComparable ”,这意味着<T. 因此,在 Wikipedia 页面中扮演集合 S 的角色,当整体考虑 的T值时,四个要点必须成立。T

显然,NaN 与其他双精度值完全不同,但它们仍然是可能的值。我在标准中没有看到任何东西(它很大,1719 页,因此这个问题和语言律师标签)在数学上得出的结论std::min是,只要不涉及 NaN,双打就可以了。

实际上,有人可以争辩说 NaN 很好,而其他双打是问题所在!事实上,回想一下,有几个可能的 NaN 双精度值(其中 2^52 - 1 个,每个都携带不同的有效负载)。考虑包含所有这些值和一个“正常”双精度值的集合 S,例如 42.0。在符号中,S = { 42.0, NaN_1, ..., NaN_n }。事实证明,这<是对 S 的严格弱排序(证明留给读者)。C++ 委员会在指定std::min为“请不要使用任何其他值,否则严格的弱排序被破坏并且行为std::min未定义”时是否考虑了这组值?我敢打赌不是,但我更愿意在标准中阅读此内容,而不是推测“某些值”的含义。

更新 2:

std::min将(上)的声明与clamp24.7.9的声明进行对比:

template<class T> constexpr const T& clamp(const T& v, const T& lo, const T& hi);
要求: 的值lo不得大于hi。对于第一种形式,类型 T 应为Cpp17LessThanComparable(表 24)。[...]
[注意:如果NaN避免, T 可以是浮点类型。——尾注]

在这里,我们清楚地看到“std::clamp只要不涉及 NaN 就可以使用双打”。我正在寻找相同类型的句子std::min

值得注意的是 Barry 在他的帖子中提到的 [structure.requirements]/8 段。显然,这是在来自P0898R0的 C++17 之后添加的):

本文档中定义的任何概念所需的操作不必是全部功能;也就是说,所需操作的某些参数可能会导致无法满足所需的语义。[示例:StrictTotallyOrdered<概念 (17.5.4) 的必需运算符在对 NaN 进行操作时不满足该概念的语义要求。— 结束示例] 这不会影响类型是否满足概念。

这是解决我在这里提出的问题的明确尝试,但在概念的背景下(正如 Barry 所指出的,Cpp17LessThanComparable不是一个概念)。此外,恕我直言,这一段也缺乏精确性。

标签: c++floating-pointlanguage-lawyerundefined-behaviorc++-standard-library

解决方案


在新的[concepts.equality]中,在稍微不同的上下文中,我们有:

如果给定相等的输入,表达式会产生相等的输出,则表达式是保持等式的。表达式的输入是表达式操作数的集合。表达式的输出是表达式的结果和表达式修改的所有操作数。

并非所有输入值都需要对给定表达式有效;例如,对于整数ab,表达式在isa / b时定义不明确。这并不排除表达式是保持平等的。表达式的是需要明确定义表达式的一组输入值。b0a / b

虽然表达式域的概念并未在整个标准中完全表达,但这是唯一合理的意图:句法要求是类型的属性,语义要求是实际值的属性。

更一般地说,我们还有[structure.requirements]/8

本文档中定义的任何概念所需的操作不必是全部功能;也就是说,所需操作的某些参数可能会导致无法满足所需的语义。[ <em>示例:在对s进行操作时,概念的必需<运算符StrictTotallyOrdered([concept.stricttotallyordered]) 不满足该概念的语义要求。NaN— <em>end example ] 这不会影响类型是否满足概念。

这具体指的是概念,而不是像Cpp17LessThanComparable这样的命名需求,但这是理解库如何工作的正确精神。


Cpp17LessThanComparable给出语义要求时

<是严格的弱排序关系 (24.7)

违反这一点的唯一方法是提供一对违反严格弱排序要求的值。对于像这样的类型double,那就是NaN. min(1.0, NaN)是未定义的行为——我们违反了算法的语义要求。NaN但是对于没有,的浮点数< 一个严格的弱排序 - 所以这很好......你可以使用min, max, sort, 任何你喜欢的。

展望未来,当我们开始编写使用 的算法时,operator<=>域的概念是表达句法要求的原因之一ConvertibleTo<decltype(x <=> y), weak_ordering>是错误的要求。be很好,它x <=> y只是partial_ordering看到了一对 is 不是的值x <=> ypartial_ordering::unordered至少我们可以通过 诊断[[ assert: (x <=> y) != partial_ordering::unordered ]];


推荐阅读