Предположим, что мы хотим использовать наши собственные пользовательские обработчики ошибок, а не какие-либо из стандартных, предоставляемых библиотекой. Если мы установим политику для определенного типа ошибки<user_error
>, библиотека вызовет обработчика ошибок, предоставленного пользователем. Они объявлены вперед, но не определены в boost/math/policies/error_handling.hpp следующим образом:
namespace boost{ namespace math{ namespace policies{
template <class T>
T user_domain_error(const char* function, const char* message, const T& val);
template <class T>
T user_pole_error(const char* function, const char* message, const T& val);
template <class T>
T user_overflow_error(const char* function, const char* message, const T& val);
template <class T>
T user_underflow_error(const char* function, const char* message, const T& val);
template <class T>
T user_denorm_error(const char* function, const char* message, const T& val);
template <class T>
T user_evaluation_error(const char* function, const char* message, const T& val);
template <class T, class TargetType>
T user_rounding_error(const char* function, const char* message, const T& val, const TargetType& t);
template <class T>
T user_indeterminate_result_error(const char* function, const char* message, const T& val);
}}}
Итак, первая задача состоит в том, чтобы включить заголовок, который мы хотим использовать, а затем предоставить определения для наших пользовательских обработчиков ошибок, которые мы хотим использовать. Мы предоставляем только наши специальные обработчики ошибок домена и полюса; другие ошибки, такие как переполнение и недоток, используют по умолчанию.
#include <boost/math/special_functions.hpp>
namespace boost{ namespace math
{
namespace policies
{
template <class T>
T user_domain_error(const char* function, const char* message, const T& val)
{
cerr << "Domain Error!" << endl;
return std::numeric_limits<T>::quiet_NaN();
}
template <class T>
T user_pole_error(const char* function, const char* message, const T& val)
{
cerr << "Pole Error!" << endl;
return std::numeric_limits<T>::quiet_NaN();
}
}
}}
Теперь нам нужно определить подходящую политику, которая вызовет этих обработчиков, и определить некоторые функции пересылки, которые используют политику:
namespace mymath{
using namespace boost::math::policies;
typedef policy<
domain_error<user_error>,
pole_error<user_error>
> user_error_policy;
BOOST_MATH_DECLARE_SPECIAL_FUNCTIONS(user_error_policy)
}
Теперь у нас есть набор функций пересылки, определенных в мимате пространства имен, которые выглядят примерно так:
template <class RealType>
inline typename boost::math::tools::promote_args<RT>::type
tgamma(RT z)
{
return boost::math::tgamma(z, user_error_policy());
}
Поэтому, когда мы звоним<mymath::tgamma(z)
>, мы действительно звоним<boost::math::tgamma(z,
user_error_policy())
>, и любые ошибки будут направлены на наших собственных обработчиков ошибок.
int main()
{
cout << "Result of erf_inv(-10) is: "
<< mymath::erf_inv(-10) << endl;
cout << "Result of tgamma(-10) is: "
<< mymath::tgamma(-10) << endl;
}
Какие выходы:
Domain Error!
Pole Error!
Result of erf_inv(-10) is: 1.#QNAN
Result of tgamma(-10) is: 1.#QNAN
Предыдущий пример был хорош и хорош, но пользовательские обработчики ошибок на самом деле не приносили пользы. В этом примере мы реализуем все пользовательские обработчики и покажем, как предоставленная им информация может быть использована для создания хороших отформатированных сообщений об ошибках.
Каждый обработчик ошибок имеет общую форму:
template <class T>
T user_error_type(
const char* function,
const char* message,
const T& val);
и принимает три аргумента:
- const char* function
Эта строка содержит один или более спецификаторов формата %1%, которые должны быть заменены именем реального типа T, например, float или double.
- const char* message
Сообщение, связанное с ошибкой, обычно содержит спецификатор формата %1%, который следует заменить значением: Однако обратите внимание, что сообщения о переполнении и недопотоке не содержат этого спецификатора %1% (поскольку значениезначениев этих случаях несущественно.
- const T& value
Значение, которое вызвало ошибку: либо аргумент функции, если это ошибка домена или полюса, предварительный результат, если это ошибка денормы или оценки, или ноль или бесконечность для ошибок перетока или перетока.
Как и прежде, мы включим заголовки, которые нам нужны:
#include <boost/math/special_functions.hpp>
Далее мы реализуем наши собственные обработчики ошибок для каждого типа ошибок, начиная с ошибок домена:
namespace boost{ namespace math{
namespace policies
{
template <class T>
T user_domain_error(const char* function, const char* message, const T& val)
{
Начнем с защитного программирования, если функция или сообщение пусты.
if(function == 0)
function = "Unknown function with arguments of type %1%";
if(message == 0)
message = "Cause unknown with bad argument %1%";
Далее мы отформатируем название функции с именем типа T, возможно, двойным:
std::string msg("Error in function ");
msg += (boost::format(function) % typeid(T).name()).str();
Затем также отформатируйте сообщение об ошибке со значением параметраval, убедившись, что мы выведем все потенциально значимые цифрыval:
msg += ": \n";
int prec = 2 + (std::numeric_limits<T>::digits * 30103UL) / 100000UL;
msg += (boost::format(message) % boost::io::group(std::setprecision(prec), val)).str();
Теперь нам просто нужно что-то сделать с сообщением, мы можем сделать исключение, но для целей этого примера мы просто сбросим сообщение на std::cerr:
std::cerr << msg << std::endl;
Наконец, единственное разумное значение, которое мы можем получить от ошибки домена, это NaN:
return std::numeric_limits<T>::quiet_NaN();
}
Ошибки поляков по существу являются частным случаем ошибок домена, поэтому в этом примере мы просто вернем результат ошибки домена:
template <class T>
T user_pole_error(const char* function, const char* message, const T& val)
{
return user_domain_error(function, message, val);
}
Ошибки переполнения очень похожи на ошибки домена, за исключением того, что в параметресообщениянет спецификатора формата %1%:
template <class T>
T user_overflow_error(const char* function, const char* message, const T& val)
{
if(function == 0)
function = "Unknown function with arguments of type %1%";
if(message == 0)
message = "Result of function is too large to represent";
std::string msg("Error in function ");
msg += (boost::format(function) % typeid(T).name()).str();
msg += ": \n";
msg += message;
std::cerr << msg << std::endl;
return val;
}
Ошибки оттока во многом такие же, как и оттоки:
template <class T>
T user_underflow_error(const char* function, const char* message, const T& val)
{
if(function == 0)
function = "Unknown function with arguments of type %1%";
if(message == 0)
message = "Result of function is too small to represent";
std::string msg("Error in function ");
msg += (boost::format(function) % typeid(T).name()).str();
msg += ": \n";
msg += message;
std::cerr << msg << std::endl;
return val;
}
Денормализованные результаты во многом похожи на отток:
template <class T>
T user_denorm_error(const char* function, const char* message, const T& val)
{
if(function == 0)
function = "Unknown function with arguments of type %1%";
if(message == 0)
message = "Result of function is denormalised";
std::string msg("Error in function ");
msg += (boost::format(function) % typeid(T).name()).str();
msg += ": \n";
msg += message;
std::cerr << msg << std::endl;
return val;
}
Что оставляет нас с ошибками оценки: они возникают, когда возникает внутренняя ошибка, которая предотвращает полную оценку функции. Параметрвалсодержит наиболее близкое приближение к найденному на данный момент результату:
template <class T>
T user_evaluation_error(const char* function, const char* message, const T& val)
{
if(function == 0)
function = "Unknown function with arguments of type %1%";
if(message == 0)
message = "An internal evaluation error occurred with "
"the best value calculated so far of %1%";
std::string msg("Error in function ");
msg += (boost::format(function) % typeid(T).name()).str();
msg += ": \n";
int prec = 2 + (std::numeric_limits<T>::digits * 30103UL) / 100000UL;
msg += (boost::format(message) % boost::io::group(std::setprecision(prec), val)).str();
std::cerr << msg << std::endl;
return std::numeric_limits<T>::quiet_NaN();
}
}
}}
Теперь нам нужно определить подходящую политику, которая вызовет этих обработчиков, и определить некоторые функции пересылки, которые используют политику:
namespace mymath
{
using namespace boost::math::policies;
typedef policy<
domain_error<user_error>,
pole_error<user_error>,
overflow_error<user_error>,
underflow_error<user_error>,
denorm_error<user_error>,
evaluation_error<user_error>
> user_error_policy;
BOOST_MATH_DECLARE_SPECIAL_FUNCTIONS(user_error_policy)
}
Теперь у нас есть набор функций пересылки, определенных в мимате пространства имен, которые выглядят примерно так:
template <class RealType>
inline typename boost::math::tools::promote_args<RT>::type
tgamma(RT z)
{
return boost::math::tgamma(z, user_error_policy());
}
Поэтому, когда мы звоним<mymath::tgamma(z)
>, мы действительно в конечном итоге звоним<boost::math::tgamma(z,
user_error_policy())
>, и любые ошибки будут направлены на наших собственных обработчиков ошибок:
int main()
{
cout << "Result of erf_inv(-10) is: "
<< mymath::erf_inv(-10) << std::endl << endl;
cout << "Result of tgamma(-10) is: "
<< mymath::tgamma(-10) << std::endl << endl;
cout << "Result of tgamma(3000) is: "
<< mymath::tgamma(3000) << std::endl << endl;
cout << "Result of tgamma(-190.5) is: "
<< mymath::tgamma(-190.5) << std::endl << endl;
}
Какие выходы:
Error in function boost::math::erf_inv<double>(double, double):
Argument outside range [-1, 1] in inverse erf function (got p=-10).
Result of erf_inv(-10) is: 1.#QNAN
Error in function boost::math::tgamma<long double>(long double):
Evaluation of tgamma at a negative integer -10.
Result of tgamma(-10) is: 1.#QNAN
Error in function boost::math::tgamma<long double>(long double):
Result of tgamma is too large to represent.
Error in function boost::math::tgamma<double>(double):
Result of function is too large to represent
Result of tgamma(3000) is: 1.#INF
Error in function boost::math::tgamma<long double>(long double):
Result of tgamma is too large to represent.
Error in function boost::math::tgamma<long double>(long double):
Result of tgamma is too small to represent.
Result of tgamma(-190.5) is: 0
Обратите внимание, что некоторые вызовы приводят к тому, что обработчик ошибок вызывается более одного раза или для вызова более одного обработчика: это артефакт того факта, что многие функции реализованы с точки зрения одной или нескольких подпрограмм, каждая из которых может иметь свою собственную обработку ошибок. Например,<tgamma(-190.5)
>реализуется в терминах<tgamma(190.5)
>— который переполняет — формула отражения для<tgamma
>затем замечает, что она делится на бесконечность и поэтому оттоки.