【问题标题】:Why does Release/Debug have a different result for std::min?为什么发布/调试对 std::min 有不同的结果?
【发布时间】:2017-02-16 13:27:40
【问题描述】:

这是测试程序:

void testFunc()
{
    double maxValue = DBL_MAX;
    double slope = std::numeric_limits<double>::quiet_NaN();

    std::cout << "slope is " << slope << std::endl;
    std::cout << "maxThreshold is " << maxValue << std::endl;
    std::cout << "the_min is " << std::min( slope, maxValue) << std::endl;
    std::cout << "the_min is " << std::min( DBL_MAX, std::numeric_limits<double>::quiet_NaN()) << std::endl;
}

int main( int argc, char* argv[] )
{
    testFunc();
    return 0;
}

在调试中,我得到:

slope is nan
maxThreshold is 1.79769e+308
the_min is nan
the_min is 1.79769e+308

在发布时,我得到:

slope is nan
maxThreshold is 1.79769e+308
the_min is 1.79769e+308
the_min is nan

为什么我会在 Release 中得到与 Debug 不同的结果?

我已经检查过 Stack Overflow 帖子 Use of min and max functions in C++,它没有提及任何发布/调试差异。

我正在使用 Visual Studio 2015。

【问题讨论】:

  • 我认为问题不在于std::min,而在于std::numeric_limits&lt;double&gt;::quiet_NaN()
  • 我记得 cl 的 64 位版本在发布模式下无法正确处理 NaN,但那是在 VS 2008 中。我希望他们从那时起修复它。
  • 我不确定NaN 是否适合LessThanComparable
  • 我想这两种行为都是有效的,因为 nan 的实际表示是实现定义的。你不应该和 nan 比较,即使是另一个 nan。
  • 对于 GCC,您可以在编译器上设置 -frounding-math -fsignaling-nans,以确保严格遵守 IEEE 754。这样,它不应该重新排序可能由于 NaN 而改变行为的比较(或任何其他优化)。对于 Visual Studio,/fp:precise 似乎是等价的(承诺不会优化诸如 x-x=0 之类的东西)。

标签: c++ nan min floating-point-comparison


【解决方案1】:

IEEE 754 中,将 NAN 与任何东西进行比较总是会产生 false,无论它是什么。

slope > 0; // false
slope < 0; // false
slope == 0; // false

而且,对你来说更重要的是

slope < DBL_MAX; // false
DBL_MAX < slope; // false

因此,编译器似乎重新排序参数/使用&gt;&lt;= 而不是&lt;,这就是您得到不同结果的原因。

例如,这些功能可以这样描述

发布:

double const& min(double const& l, double const r) {
    return l <= r ? l : r;
}

调试:

double const& min(double const& l, double const& r) {
    return r < l ? r : l;
}

除了std::min 的要求(LessThanComparable)之外,它们在算术上具有相同的含义。但是当您将它们与 NaN 一起使用时,它们会产生不同的结果。

【讨论】:

  • 对任何可能感兴趣的人的补充一点:在 IEEE754 中,将 NAN 与任何包括其自身进行比较总是会产生false。顺便说一句,this SO question 的姿势很好,并且有一组有趣的答案 - 包括在 cmets 中的几场非常有趣的争吵。
  • 我正要评论“任何东西”包含错误,但是当我检查 NaN ∈ 任何东西时,我得到了false
  • @krzaq 尝试检查 NaN ∉ 任何东西。
  • @krzaq 不应该交换r : l 而不是r &lt; l,否则它仍然评估为假并返回l? IE。 r &lt; l ? r : l; -> r &gt; l ? l : r;
  • 肯定有问题。我将不得不绕开这个问题,因为它还必须适合 备注:当参数相等时返回第一个参数。 两次。
【解决方案2】:

您没有指定处理器使用哪种浮点表示格式。但是,由于您使用 Visual Studio,我将假设您使用 Windows,然后我将假设您的处理器使用 IEEE 754 表示。

在 IEEE 754 中,NaN 对于每个数字都是无序的。这意味着(NaN &lt; f) == false(f &lt; NaN) == false 对于f 的任何值。迂腐地,这意味着支持 NaN 的浮点数不满足 LessThanComparable 的要求,而 std::min 的要求是。实际上,只要两个参数都不是 NaN,std::min 的行为就符合标准中的规定。

由于代码中的参数之一是 NaN,因此标准未指定结果 - 它可能是一个或另一个,具体取决于任何外部因素,例如发布与调试构建、编译器版本、月相、等等

【讨论】:

  • 在我看来,浮点数满足了 LessThanComparable 的所有要求。 LessThanComparable 不需要总订单;它只需要严格的弱排序。任何涉及 NaN 的比较都返回 false 的事实并不违反任何要求。 (这都忽略了信号 NaN,任何使用都是 UB。)
  • @user2357112 进一步检查规则,我认为您确实是正确的。我会修正答案。
  • 等等,实际上,看起来 cppreference 页面 是错误的,并且多年来它一直缺少严格的弱排序要求之一:将 equiv(a, b) 定义为!(a &lt; b) &amp;&amp; !(b &lt; a),要求equiv(a, b) &amp;&amp; equiv(b, c) 隐含equiv(a, c)。 NaN 违反了该要求,因此看起来浮点数确实不满足 LessThanComparable。
【解决方案3】:

知道了:

这是 VS 在调试模式下使用的实现(_PredDEBUG_LT,LT 表示低于):

template<class _Pr,
    class _Ty1,
    class _Ty2> inline
    _CONST_FUN bool _Debug_lt_pred(_Pr _Pred,
        _Ty1&& _Left, _Ty2&& _Right,
        _Dbfile_t _File, _Dbline_t _Line)
    {   // test if _Pred(_Left, _Right) and _Pred is strict weak ordering
    return (!_Pred(_Left, _Right)
        ? false
        : _Pred(_Right, _Left)
            ? (_DEBUG_ERROR2("invalid comparator", _File, _Line), true)
            : true);
    }

相当于(更具可读性):

    if (!_Pred(_Left, _Right))
    {
        return false;
    }
    else
    {
        if ( _Pred(_Right, _Left) )
        {
            assert( false );
            return true;
        }
        else
        {
            return true;
        }
    }

这又相当于(!_Pred(_Left, _Right))。转录为宏,它变为#define _DEBUG_LT(x, y) !((y) &lt; (x))(即:NOT right

发布实现其实是一个宏#define _DEBUG_LT(x, y) ((x) &lt; (y))(即:左

所以 Debug (!(y&lt;x)) 和 Release (x&lt;y) 实现肯定是不一样的,如果一个参数是 NaN,它们的行为会有所不同......!不要问他们为什么这样做......

【讨论】:

  • 不错的侦探。
  • 好吧,他们这样做是为了捕捉错误。在调试版本中,它以两种方式评估表达式,并断言它们产生相同的答案。在发布版本中,您假设应用程序已经调试并且比较器是正确的,因此您不想为进行两次比较而付出代价。
  • 当然,但至少,他们可以确保两者最终得到相同的结果......这可以通过以不同的顺序检查两种方式来实现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-19
  • 2016-05-14
  • 2015-01-24
  • 2020-01-15
  • 2021-11-10
  • 2018-11-29
  • 1970-01-01
相关资源
最近更新 更多