【问题标题】:NaN or false as double precision return valueNaN 或 false 作为双精度返回值
【发布时间】:2014-07-05 00:38:14
【问题描述】:

我有一个返回双精度值的函数。在某些情况下,结果为零,因此应在调用者路由中相应地处理此结果。我想知道返回零(NaN、false 等!)代替双值数字的正确方法是什么:

double foo(){
    if (some_conditions) {
        return result_of_calculations;
    } else {
        // Which of the following is better?
        return std::numeric_limits<double>::quiet_NaN(); // (1)
        return 0;                                        // (2)
        return false;                                    // (3)
        return (int) 0;                                  // (4)
    }
}

调用例程是这样的:

double bar = foo();
if (bar == 0) {
    // handle the zero case 
} else {
    // handle the non-zero case 
}

if (bar == 0) 与#3 和#4 一起使用是否安全?我可以将它与#2 一起使用,还是应该执行fabs(bar - 0) &lt; EPSILON 之类的操作?

quiet_NaN的情况应该如何处理?我在这个网站(例如12)中读到,NaN 的比较不是总是错误的。那该怎么办?

总之,如何返回一个假值来代替双精度值以供以后比较?

【问题讨论】:

  • 请注意,返回 0 的所有三种方式都会向调用者返回相同的 double,因此您的问题归结为“0 vs. NaN”,然后问题是 0 是否是有效的返回值.如果是,你可能想看看 boost::optional
  • @PlasmaHH,我明白你的意思。 boost 然而,恐怕是不可能的。我正在寻找一种纯 c++ 方法。
  • 上次我检查 boost 是一个库可以得到的纯 C++...
  • @PlasmaHH:我认为 OP 实际上意味着不使用任何第三方库,而只使用 stdlib。
  • 如果零情况是异常行为,则抛出异常并让 bar 或其他任何人处理它。但是,如果零案例是预期的正常行为的结果,请不要这样做,而只是应该处理的单独案例。

标签: c++ nan logical-operators double-precision


【解决方案1】:

您不应将函数计算的实际值与进一步描述结果的附加消息(即错误状态)混在一起。


好的方法是:

  1. 自定义结果结构

    struct foo_result_t
    {
        double value;
        bool state;
    };
    
    foo_result_t foo() {
        foo_result_t r;
        r.value = result_of_calculations;
        r.state = some_conditions;
        return r;
    }
    

    这可能是最好的方法,如果您需要从您的函数中获得更多信息,它很容易扩展。就像指出它实际上失败的原因一样。

    对于简单的情况,也可以考虑使用std::pair&lt;double,bool&gt;

    您也可以使用boost::optional (link)。

  2. C++异常方式

    double foo() {
        if(some_conditions) {
            return result_of_calculations;
        }
        else {
            throw some_exception;
        }
    }
    

    这看起来很优雅,但有时​​您可能希望保持代码无异常。

  3. 老式 C 风格的方式:

    bool foo(double& result) {
        if(some_conditions) {
            result = result_of_calculations;
            return true;
        }
        else {
            return false;
        }
    }
    

    这可能是最直接的一个并且没有开销(例外,附加结构)。但它看起来有点奇怪,因为函数试图返回两个值,但其中一个实际上是一个参数。

【讨论】:

  • bools 不是那个老派!但我同意你的观点。 +1
  • 我想我会选择返回 bool。但除此之外,NaN 呢?
  • 返回一个不同的值来表示某种状态并没有错(参见std::iostream)。在返回浮点数的函数中,NaN 就可以了。
  • @Pouya:关于nan,你想知道什么?
【解决方案2】:

没有足够的上下文来完全回答这个问题。当some_conditions 评估为假时,听起来您希望返回哨兵。但正确的哨兵是什么?答案主要取决于foo() 的使用位置和方式。

在大多数情况下,正确答案不是以上任何一种。也许抛出异常会更好。也许将接口更改为bool foo(double&amp;)bool foo(double*) 会更好。也许,正如 PlasmaHH 建议的那样,将其更改为 boost::optional&lt;double&gt; foo() 会更好。

如果哨兵更好,正确的哨兵将取决于价值。假设调用者正在加法或乘法。如果不影响结果,则标记应分别为 0.0 或 1.0。如果它应该完全抛弃结果,NaN 或 Inf 可能更有意义。

至于 EPSILON 的问题,这取决于您希望如何处理其他情况返回的接近零的值。返回文字 0.0 值将导致与 0.0 完全比较的双精度值;但是,“应该”产生0.0 的计算可能不会完全产生0.0

【讨论】:

  • 我明白了...但是 NaN 呢?
  • @Pouya NaN 呢?我更愿意编写一个消费者,它不必在它会在计算中使用的哨兵值上进行分支,所以虽然std::isnan 有效,但我更愿意避免使用它。
【解决方案3】:

if (bar == 0) 与 #3 和 #4 一起使用是否安全?

#3#4#2 编译相同。

我可以将它与 #2 一起使用,还是我应该做类似 fabs(bar - 0)

是的(你可以和#2一起使用):double bar = 0; => bar == 0;

但是!如果some_conditions == true 分支可以通过某种计算返回(接近)零,并且您需要以相同的方式处理这种情况,那么您需要在比较浮点值相等时使用通常的技巧。

另一方面,如果some_conditions == true 分支可能返回零,但这种情况的处理方式应不同于false 分支的结果,那么您需要使用另一种方法。 Danvil 列出了有用的替代方案。

至于比较NaN,使用isnan

【讨论】:

    【解决方案4】:

    鉴于此示例代码,

    double foo(){
        if (some_conditions) {
            return result_of_calculations;
        } else {
            // Which of the following is better?
            return std::numeric_limits<double>::quiet_NaN(); // (1)
            return 0;                                        // (2)
            return false;                                    // (3)
            return (int) 0;                                  // (4)
        }
    }
    

    在情况 2、3 和 4 返回相同值的情况下,很明显设计不是基于 C++ 的知情视图

    因此我建议更改设计。

    我通常会做以下事情:

    auto can_foo()
        -> bool
    { return (some_conditions); }
    
    auto foo()
        -> double
    {
        assert( can_foo() );
        return result_of_calculations;
    }
    

    然而,在某些情况下,can_foo试图 执行 foo 密不可分,在这种情况下我会抛出一个异常:

    auto hopefully( bool const cond ) -> bool { return cond; }
    auto fail( string const& s ) -> bool { throw runtime_error( s ); }
    
    auto foo()
        -> double
    {
        double const result = calculations();
        hopefully( calculations_succeeded() )
            || fail( "foo: calculations failed" );
        return result;
    }
    

    foo 中已经抛出异常的替代方法是返回 boost::optional 或其他基于 Barton & Nackman Fallible 类的对象。使用这种方法,实际的抛出与否被委托给客户端代码。请注意,如果您不关心效率,实现 Optional 类是微不足道的:只需使用 std::vector 作为价值载体。


    返回 NaN 不是一个好主意,因为很难对 NaN 进行便携式测试。这主要是因为主要编译器的优化开关使它们在报告它们符合标准时以不符合的方式运行。

    【讨论】:

    • 两个问题:1.这是c++11吗? 2.你的最后一段是否意味着-O3fast-math会破坏std::isnan
    • @Pouya:是的,这是 C++11,但是 fast-math 是否会破坏 std::isnan 这取决于编译器。更重要的是,是否要求浮点优化会破坏 C++11 之前的 NaN 测试方式。更清楚的是:是的,在某些情况下确实如此。
    • 我不同意:In some cases, however, the can_foo is inextricably bound up with attempting to do foo, and in such a case I would just throw an exception - 一个不同的返回值也可以处理这种情况。
    猜你喜欢
    • 2019-07-14
    • 1970-01-01
    • 2012-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-31
    • 2010-10-05
    • 2019-08-27
    相关资源
    最近更新 更多