NaN or false as double precision return value
Asked Answered
S

4

0

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?

Stine answered 16/5, 2014 at 14:7 Comment(5)
Note that all three ways to return 0 return an identical double to the caller, so your question boils down to "0 vs. NaN" and then the question is if 0 would be an otherwise valid return value. If it is, you might want to have a look at boost::optionalPythagoreanism
@PlasmaHH, I see your point. boost however, I'm afraid is not possible. I'm seeking for a pure c++ approach.Stine
Last time I checked boost was as pure C++ as a library can get...Pythagoreanism
@PlasmaHH: I think the OP actually means not to use any 3rd party libraries but only the stdlib.Unknow
if the zero case is exceptional behavior throw an exception and let bar or whoever else handle it. However, do not do this if the zero case is a result of expected normal behavior, just a separate case that should be handled.Ecesis
E
3

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.

Elfriedeelfstan answered 16/5, 2014 at 14:19 Comment(4)
bools aren't that old school! But I agree with your point. +1Bema
I think I will go with returning bool. But apart from that, what about NaN?Stine
There is nothing wrong returning a distinct value to indicate a certain state (see std::iostream). In a function returning a floating point, NaN is just fine.Disrepair
@Pouya: What do you want to know about nan?Elfriedeelfstan
M
1

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.

Magalymagan answered 16/5, 2014 at 14:19 Comment(2)
I see... But what about NaN?Stine
@Stine What about NaN? I would prefer to write a consumer that doesn't have to branch on a sentinel value it would otherwise use in a calculation, so while std::isnan works, I prefer to avoid needing to use it.Magalymagan
B
1

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

Byrdie answered 16/5, 2014 at 14:21 Comment(0)
L
1

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.

Literalminded answered 16/5, 2014 at 14:39 Comment(3)
two questions: 1. is this c++11? 2. does your final paragraph means that -O3 or fast-math will break std::isnan?Stine
@Pouya: yes, this is C++11, but re whether fast-math breaks std::isnan that depends on the compiler. more important, does asking for floating point optimization break pre-C++11 ways of testing for NaN. there it's more clear: yes in some cases it does.Literalminded
I do not agree to: 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 - a distinct return value can handle this case, too.Disrepair

© 2022 - 2024 — McMap. All rights reserved.