NaN or false as double precision return value

梦想与她 提交于 2021-01-29 18:27:22

问题


I have a function that returns a double value. In some cases the result is zero, and this results should be handled in the caller routing accordingly. I was wondering what is the proper way of returning zero (NaN, false, etc!) in place of double value number:

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)
    }
}

The caller routine is something like so:

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

Is if (bar == 0) safe to use with #3 and #4? Can I use it with #2 or I should do something like fabs(bar - 0) < EPSILON?

How should one handle the case of quiet_NaN? I read in this site (e.g. 1, 2) that comparison of NaN is not necessary always false. What to do with that?

In summary, how would one returns a false value in place of a double for later comparison?


回答1:


You should not mix the actual value the function computes with an additional message which further describes the result, i.e. an error state.


Good ways are:

  1. a custom result struct

    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;
    }
    

    This is perhaps the best way and is easily extendable if you need even more information from your function. Like indicating why it actually failed.

    For simple cases, you can also consider using std::pair<double,bool>.

    You could also use boost::optional (link).

  2. the C++ exception way

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

    This looks and is elegant, but sometimes you might want to keep your code exception free.

  3. the old-school C-style way:

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

    This is perhaps the most direct one and has no overhead (exceptions, additional struct). But it looks a bit strange as the function tries to return two values, but one of them is actually an argument.




回答2:


There is not enough context to fully answer this question. It sounds like you desire to return a sentinel when some_conditions evaluates to false. But what is the correct sentinel? Answering that depends primarily on where and how foo() will be used.

In most cases, the correct answer is none of the above. Perhaps throwing an exception would be better. Perhaps changing the interface to bool foo(double&) or bool foo(double*) would be better. Perhaps, as PlasmaHH suggests, changing it to boost::optional<double> foo() would be better.

If a sentinel is better, the right sentinel will depend on the value. Say the caller is adding or multiplying. If it shouldn't affect the results, the sentinel should be 0.0 or 1.0 respectively. If it should throw the results off completely, NaN or Inf may make more sense.

As for the question of EPSILON, that depends on how you want to treat near-zero values returned by the other case. Returning a literal 0.0 value will result in a double that compares exactly with 0.0; calculations that "should" result in 0.0, however, may not result in exactly 0.0.




回答3:


Is if (bar == 0) safe to use with #3 and #4?

#3 and #4 compile identically to #2.

Can I use it with #2 or I should do something like fabs(bar - 0) < EPSILON

Yes (you can use it with #2): double bar = 0; => bar == 0;

However! If some_conditions == true branch can return (near) zero through some calculation and you need to handle that situation in the same way, then you need to use the usual tricks when comparing equality of floating point values.

On the other hand, if the some_conditions == true branch may return zero, but that case should be handled differently than the result from false branch, then you need to use another approach. Danvil has listed useful alternatives.

As for comparing NaN, use isnan




回答4:


Given this example code,

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)
    }
}

where cases 2, 3, and 4 return the same value, it's clear that the design is not based on an informed view of C++.

Thus I recommend changing the design.

I would generally do the following:

auto can_foo()
    -> bool
{ return (some_conditions); }

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

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:

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;
}

An alternative to throwing an exception already in foo is to return a boost::optional or other object based on the Barton & Nackman Fallible class. With this approach the actual throwing or not is delegated up to the client code. Note that it's trivial to implement an Optional class if you don't care about efficiency: just use a std::vector as value carrier.


Returning NaN is not a good idea, because it's difficult to test portably for NaN. Mostly that's because of optimization switches for the main compilers that make them behave in non-conforming ways while reporting that they're conforming to the standard.



来源:https://stackoverflow.com/questions/23697311/nan-or-false-as-double-precision-return-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!