Возврат NaN или исключение?

У меня есть функция, которая получает выборку (std::vector<double>) в качестве входных данных и вычисляет среднее значение выборки: как лучше всего обрабатывать случай с пустым входным вектором?

Моя первая идея — создать исключение, как в этом фрагменте:

double average(const std::vector<double>& sample)
{
   size_t sz = sample.size();
   if (sz==0) throw std::exception("unexpected empty vector");

   double acc = 0;
   for (size_t i=0; i<sz; ++i) acc += sample[i];
   return acc/sz;
}

Но я думаю, что другим решением может быть возврат NaN:

double average(const std::vector<double>& sample)
{
   size_t sz = sample.size();
   if (sz==0) return std::numeric_limits<double>::quiet_NaN();

   double acc = 0;
   for (size_t i=0; i<sz; ++i) acc += sample[i];
   return acc/sz;
}

Мне нравится исключение, потому что оно показывает, где возникла проблема, в то время как если я получу NaN в конечном результате долгого вычисления, мне будет сложнее понять, где родился NaN. Во всяком случае, с NaN мне нравится возможность вернуть «особый» двойной сигнал, чтобы сигнализировать о том, что произошло что-то неожиданное.

Есть ли другой способ справиться с пустым вектором? Спасибо.


person Alessandro Jacopson    schedule 03.09.2011    source источник
comment
2 совета: передать вектор по ссылке (&) и использовать суммирование Каана.   -  person Yakov Galka    schedule 03.09.2011
comment
@ybungalobill +1 за первый совет (я забыл & я обычно пишу). По второму совету у меня вопрос: видели ли вы когда-нибудь реальный код, который плохо себя ведет, и исправили проблему с суммированием Каана?   -  person Alessandro Jacopson    schedule 03.09.2011
comment
определить «плохое поведение». Вычисление с плавающей запятой не «плохое поведение» (обычно), оно просто постепенно теряет точность. Да, обычно суммируют 100 чисел и получают результат, достаточно далекий от числа с бесконечной точностью, чтобы его можно было увидеть на выходе.   -  person Yakov Galka    schedule 03.09.2011
comment
Меня интересует непосредственный опыт (или «Военная история»), связанный с числовым анализом. Случалось ли это с кодом, над которым вы работали? Насколько далеко было достаточно?   -  person Alessandro Jacopson    schedule 03.09.2011
comment
@uvts_cvs: Задайте это как новый вопрос (с тегом с плавающей запятой).   -  person Martin York    schedule 03.09.2011
comment
@uvts: ну я преувеличиваю. Хотя вы можете получить 2 ошибки после запятой при суммировании 100 чисел, это не так часто. Вот реальный пример суммирования 1/n^2 с 32-битной точностью. Без Кахана он сходится к неправильному пределу 1,6447253 около 4200-го члена, с Каханом он достигает 1,6449339 на 3600000-й итерации. Реальное значение 1,644934066...   -  person Yakov Galka    schedule 03.09.2011
comment
@Tux-D, вопрос уже задавался здесь stackoverflow.com/questions/4940072/kahan-summation, но ИМХО безуспешно...   -  person Alessandro Jacopson    schedule 06.09.2011
comment
@ybungalobill Вместо Кахана, как насчет std::accumulate( sample.begin(), sample.end(), 0.0 );?   -  person Alessandro Jacopson    schedule 06.09.2011
comment
@uvts: ну, это определенно будет лучше, чем явный цикл, который вы написали, но он делает то же самое.   -  person Yakov Galka    schedule 06.09.2011
comment
@ybungalobill Вы правы, я проверил стандарт C ++, и мне кажется, что выполнение Kahan было бы несовместимым.   -  person Alessandro Jacopson    schedule 07.09.2011


Ответы (4)


Я ДЕЙСТВИТЕЛЬНО думаю, что математически NaN было бы более правильным. В конце концов, это 0.0/0. Если бы это было прямое разделение, что бы произошло?

Имейте в виду, что о C++ и исключениях ведутся священные войны. Например, прочтите это: Выбрасывать или не выбрасывать исключения?

person xanatos    schedule 03.09.2011
comment
Я не вижу здесь войны. Два верхних ответа единственные, за кого проголосовали, оба согласны. Исключения в порядке (все зависит от ситуации и использования). - person Martin York; 03.09.2011
comment
@Tux-D, вы должны посмотреть страницу, на которую ссылается OP. И, в конце концов, С++ не совсем дружелюбен к исключениям или универсален для исключений в своих библиотеках и в языке. К сожалению, это дополнительная функция. - person xanatos; 03.09.2011

Я бы оставил поведение неопределенным.

Просто закодируйте его для непустого случая и дайте вызывающей стороне подумать о правильном его использовании. Вероятно, вы все равно не будете вызывать его для пустых векторов, так как проверка пустого ввода, вероятно, будет выполнена раньше.

person Yakov Galka    schedule 03.09.2011
comment
неопределенный? И какое значение возвращает ваша функция? - person Alessandro Jacopson; 03.09.2011
comment
@uvts: см. неопределенное поведение - person Yakov Galka; 03.09.2011

Вы правильно понимаете использование исключений, и вам следует придерживаться этого подхода. Исключения предназначены для этой цели (throw при возникновении исключительного условия).

В этом случае предположим, что если вы возвращаете NaN, то каждый раз, когда вы вызываете функцию average(), вы должны убедиться, что вы ставите дополнительную проверку, которая заботится о сценарии NaN.

[Примечание: Кроме того, убедитесь, что условие (sz == 0) не является очень частым сценарием. ИМО, я не буду использовать исключения, если они будут выдаваться часто.]

person iammilind    schedule 03.09.2011
comment
+1 Я согласен не использовать исключения, если они часто выдаются - person Alessandro Jacopson; 03.09.2011
comment
каждый раз, когда вы вызываете функцию medium(), вы должны убедиться, что вы ставите дополнительную проверку, которая заботится о сценарии NaN - скорее всего, вызывающая сторона гарантирует, что входной вектор непуст (что может не требовать никакого кода - во многих случаях будет известно, что это правда), поэтому не нужно проверять NaN. - person Steve Jessop; 03.09.2011

Я бы выкинул, просто на всякий случай и убедиться, что ошибка обнаружена сразу, а не, как вы сказали, через некоторое время.

на самом деле в этом конкретном случае использования вы можете вернуть 0, если имеет смысл сказать, что 0 является средним значением «ничего».

что мы обычно делаем, так это проверяем параметры, как только мы попадаем внутрь метода, и в случае, если мы выбрасываем ArgumentNullException или OutOfRangeException, если действительно метод был разработан для вызова только с ненулевыми и правильно заполненными аргументами.

person Davide Piras    schedule 03.09.2011
comment
Думаю, я не могу сказать, что 0 — это среднее значение ничего :-), например, 0 — это среднее значение {-1,+1}. - person Alessandro Jacopson; 03.09.2011