Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Calling User Defined Error Handlers

Suppose we want our own user-defined error handlers rather than the any of the default ones supplied by the library to be used. If we set the policy for a specific type of error to user_error then the library will call a user-supplied error handler. These are forward declared, but not defined in boost/math/policies/error_handling.hpp like this:

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

}}} // namespaces

So out first job is to include the header we want to use, and then provide definitions for the user-defined error handlers we want to use:

#include <iostream>
#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)
{
   std::cerr << "Domain Error." << std::endl;
   return std::numeric_limits<T>::quiet_NaN();
}

template <class T>
T user_pole_error(const char* function, const char* message, const T& val)
{
   std::cerr << "Pole Error." << std::endl;
   return std::numeric_limits<T>::quiet_NaN();
}


}}} // namespaces

Now we'll need to define a suitable policy that will call these handlers, and define some forwarding functions that make use of the policy:

namespace{

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)

} // close unnamed namespace

We now have a set of forwarding functions defined in an unnamed namespace that all look something like this:

template <class RealType>
inline typename boost::math::tools::promote_args<RT>::type
   tgamma(RT z)
{
   return boost::math::tgamma(z, user_error_policy());
}

So that when we call tgamma(z) we really end up calling boost::math::tgamma(z, user_error_policy()), and any errors will get directed to our own error handlers:

int main()
{
   std::cout << "Result of erf_inv(-10) is: "
      << erf_inv(-10) << std::endl;
   std::cout << "Result of tgamma(-10) is: "
      << tgamma(-10) << std::endl;
}

Which outputs:

Domain Error.
Result of erf_inv(-10) is: 1.#QNAN
Pole Error.
Result of tgamma(-10) is: 1.#QNAN

The previous example was all well and good, but the custom error handlers didn't really do much of any use. In this example we'll implement all the custom handlers and show how the information provided to them can be used to generate nice formatted error messages.

Each error handler has the general form:

template <class T>
T user_error_type(
   const char* function, 
   const char* message, 
   const T& val);

and accepts three arguments:

const char* function

The name of the function that raised the error, this string contains one or more %1% format specifiers that should be replaced by the name of type T.

const char* message

A message associated with the error, normally this contains a %1% format specifier that should be replaced with the value of value: however note that overflow and underflow messages do not contain this %1% specifier (since the value of value is immaterial in these cases).

const T& value

The value that caused the error: either an argument to the function if this is a domain or pole error, the tentative result if this is a denorm or evaluation error, or zero or infinity for underflow or overflow errors.

As before we'll include the headers we need first:

#include <iostream>
#include <boost/math/special_functions.hpp>

Next we'll implement the error handlers for each type of error, starting with domain errors:

namespace boost{ namespace math{ namespace policies{

template <class T>
T user_domain_error(const char* function, const char* message, const T& val)
{

We'll begin with a bit of defensive programming:

if(function == 0)
    function = "Unknown function with arguments of type %1%";
if(message == 0)
    message = "Cause unknown with bad argument %1%";

Next we'll format the name of the function with the name of type T:

std::string msg("Error in function ");
msg += (boost::format(function) % typeid(T).name()).str();

Then likewise format the error message with the value of parameter val, making sure we output all the digits of 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();

Now we just have to do something with the message, we could throw an exception, but for the purposes of this example we'll just dump the message to std::cerr:

std::cerr << msg << std::endl;

Finally the only sensible value we can return from a domain error is a NaN:

   return std::numeric_limits<T>::quiet_NaN();
}

Pole errors are essentially a special case of domain errors, so in this example we'll just return the result of a domain error:

template <class T>
T user_pole_error(const char* function, const char* message, const T& val)
{
   return user_domain_error(function, message, val);
}

Overflow errors are very similar to domain errors, except that there's no %1% format specifier in the message parameter:

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;
   
   // Value passed to the function is an infinity, just return it:
   return val; 
}

Underflow errors are much the same as overflow:

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;
   
   // Value passed to the function is zero, just return it:
   return val; 
}

Denormalised results are much the same as underflow:

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;
   
   // Value passed to the function is denormalised, just return it:
   return val; 
}

Which leaves us with evaluation errors, these occur when an internal error occurs that prevents the function being fully evaluated. The parameter val contains the closest approximation to the result found so far:

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

   // What do we return here?  This is generally a fatal error,
   // that should never occur, just return a NaN for the purposes
   // of the example:
   return std::numeric_limits<T>::quiet_NaN();
}

}}} // namespaces

Now we'll need to define a suitable policy that will call these handlers, and define some forwarding functions that make use of the policy:

namespace{

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)

} // close unnamed namespace

We now have a set of forwarding functions defined in an unnamed namespace that all look something like this:

template <class RealType>
inline typename boost::math::tools::promote_args<RT>::type
   tgamma(RT z)
{
   return boost::math::tgamma(z, user_error_policy());
}

So that when we call tgamma(z) we really end up calling boost::math::tgamma(z, user_error_policy()), and any errors will get directed to our own error handlers:

int main()
{
   // Raise a domain error:
   std::cout << "Result of erf_inv(-10) is: "
      << erf_inv(-10) << std::endl << std::endl;
   // Raise a pole error:
   std::cout << "Result of tgamma(-10) is: "
      << tgamma(-10) << std::endl << std::endl;
   // Raise an overflow error:
   std::cout << "Result of tgamma(3000) is: "
      << tgamma(3000) << std::endl << std::endl;
   // Raise an underflow error:
   std::cout << "Result of tgamma(-190.5) is: "
      << tgamma(-190.5) << std::endl << std::endl;
   // Unfortunately we can't predicably raise a denormalised
   // result, nor can we raise an evaluation error in this example
   // since these should never really occur!
}

Which outputs:

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

Notice how some of the calls result in an error handler being called more than once, or for more than one handler to be called: this is an artefact of the fact that many functions are implemented in terms of one or more sub-routines each of which may have it's own error handling. For example tgamma(-190.5) is implemented in terms of tgamma(190.5) - which overflows - the reflection formula for tgamma then notices that it's dividing by infinity and underflows.


PrevUpHomeNext