У меня есть функция, которая возвращает двойное значение. В некоторых случаях результат равен нулю, и эти результаты должны соответственно обрабатываться в маршрутизации вызывающего абонента. Мне было интересно, как правильно вернуть ноль (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) < EPSILON
?
Как следует обращаться со случаем quiet_NaN
? Я читаю на этом сайте (например, 1, 2) что сравнение NaN является не необходимо всегда ложно. Что с этим делать?
Таким образом, как можно вернуть ложное значение вместо двойного для последующего сравнения?
Вы не должны смешивать фактическое значение, которое вычисляет функция, с дополнительным сообщением, которое дополнительно описывает результат, то есть состояние ошибки.
Хорошие способы:
пользовательская структура результата
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<double,bool>
,
Вы также можете использовать boost::optional
(ссылка на сайт).
способ исключения C ++
double foo() {
if(some_conditions) {
return result_of_calculations;
}
else {
throw some_exception;
}
}
Это выглядит и элегантно, но иногда вы можете захотеть сохранить исключение кода.
старый стиль в стиле C:
bool foo(double& result) {
if(some_conditions) {
result = result_of_calculations;
return true;
}
else {
return false;
}
}
Это, пожалуй, самый прямой и не имеет накладных расходов (исключения, дополнительная структура). Но это выглядит немного странно, так как функция пытается вернуть два значения, но одно из них на самом деле является аргументом.
Недостаточно контекста, чтобы полностью ответить на этот вопрос. Похоже, вы хотите вернуть стражу, когда some_conditions
оценивается как ложное. Но какой правильный страж? Ответ зависит от того, где и как foo()
будет использоваться.
В большинстве случаев правильный ответ — ни один из вышеперечисленных. Возможно, выбрасывание исключения будет лучше. Возможно, изменение интерфейса на bool foo(double&)
или же bool foo(double*)
было бы лучше. Возможно, как предполагает PlasmaHH, изменив его на boost::optional<double> foo()
было бы лучше.
Если часовой лучше, правильный часовой будет зависеть от стоимости. Скажем, звонящий добавляет или умножает. Если это не должно повлиять на результаты, часовой должен быть 0,0 или 1,0 соответственно. Если это должно полностью исключить результаты, NaN или Inf могут иметь больше смысла.
Что касается вопроса EPSILON, это зависит от того, как вы хотите обрабатывать близкие к нулю значения, возвращаемые другим случаем. Возвращение буквального 0.0
значение приведет к двойному, который точно сравнивается с 0.0
; расчеты, которые «должны» привести к 0.0
Однако, может не привести к точно 0.0
,
Является ли if (bar == 0) безопасным для использования с № 3 и № 4?
#3
а также #4
компилировать идентично #2
,
Могу ли я использовать это с # 2 или я должен сделать что-то вроде Fabs (бар — 0) < EPSILON
Да (вы можете использовать его с #2
): double bar = 0;
=> bar == 0;
Тем не мение! Если some_conditions == true
В некоторых вычислениях ответвление может возвращать (около) ноль, и вам нужно обрабатывать эту ситуацию таким же образом, тогда вам нужно использовать обычные приемы при сравнении равенства значений с плавающей запятой.
С другой стороны, если some_conditions == true
ветвь может вернуть ноль, но этот случай должен обрабатываться не так, как результат false
филиал, то вам нужно использовать другой подход. Данвил перечислил полезные альтернативы.
Что касается сравнения NaN
использовать IsNaN
Учитывая этот пример кода,
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
или другой объект, основанный на Бартоне & Nackman Fallible
учебный класс. При таком подходе фактический бросок или нет делегируется клиентскому коду. Обратите внимание, что реализовать тривиально Optional
класс, если вы не заботитесь об эффективности: просто используйте std::vector
как носитель стоимости.
возврате NaN не очень хорошая идея, потому что трудно протестировать переносимо для NaN. Главным образом это происходит из-за переключателей оптимизации для основных компиляторов, которые заставляют их вести себя несоответствующими способами, сообщая, что они соответствуют стандарту.