Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

調用用戶定義的錯誤處理方法(Calling User Defined Error Handlers)

假如你想要使用自己定義的錯誤處理方法而不是這個庫提供的任何錯誤處理方法。如果我們為一個特殊類型的錯誤設置一個user_error 策略,那麼這個庫將會調用用戶提供的錯誤處理方法。這只是前向聲明(forward declared),但並沒有在 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>
T user_indeterminate_result_error(const char* function, const char* message, const T& val);

}}} // namespaces

因此我們的首要任務就是包含我們想要使用的頭文件,並為我們想要使用的錯誤處理方法提供定義:

#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

現在,我們需要定義一個合適的策略來調用這些錯誤處理方法,並定義一些前向函數(forwarding functions)來使用這個策略:

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)

} // 關閉未命名的名字空間

現在我們在一個未命名的名字空間中定義了一些前向函數(forwarding functions),類似於下面這樣:

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

因此,當我們調用函數tgamma(z) 時,我們將會最終調用boost::math::tgamma(z, user_error_policy()),並且任何的錯誤都會重定向到我們自己的錯誤處理方法:

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

輸出為:

Domain Error.
Result of erf_inv(-10) is: 1.#QNAN
Pole Error.
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的名字替換的分類符號(specifier)。

const char* message

與這個錯誤相關的信息,通常這個信息有一個 %1% 格式化的我們必須使用value值來替換的分類符號(specifier):但是請注意,向上溢出和向下溢出消息並沒有包含一個 %1%分類符號 (因為在這種情況下,這個值是無關緊要的)。

const T& value

產生這個錯誤的值:如果這是一個定義域錯誤或者是一個pole error,那麼這個值是函數的參數,如果這個錯誤是一個 denorm 或是一個計算錯誤,或是0,或是無限值(對於向上溢出和向下溢出),那麼這個值是試驗性的值(tentative result)。

像前面一樣,我們首先包含我們需要的頭文件:

#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)
{

我們使用具有一點防禦性的(defensive)編程開始:

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

Pole errors 是定義域錯誤的一個特例,所以在這個例子中,我們只是返回定義域錯誤的結果:

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

溢出錯誤類似於定義域錯誤,除了在 message 參數中沒有 %1% 格式化分類符號(format specifier):

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;
   
   // 傳遞給函數的值是一個0,僅僅將其返回:
   return val; 
}

非正規化的結果(Denormalised results)與向下溢出是一樣的:

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;
   
   //傳遞給函數的值是非正規化的( denormalised ),僅僅將其返回:
   return val; 
}

現在剩下的是對計算錯誤的處理了,這會在出現內部錯誤使得函數不能完全計算的情況下發生。參數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 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;

   // 在這裡我們返回什麼呢?  這通常是永遠都不應該發生的一個嚴重的錯誤,
   // 出於這個例子的目的,返回NaN:
   return std::numeric_limits<T>::quiet_NaN();
}

}}} // namespaces

現在我們需要定義一個合適的策略來調用這些錯誤處理方法,並定義一些前向函數(forwarding function)來使用這個策略:

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)

} // 關閉未命名的名字空間

現在在未命名的名字空間中有一些前向函數( forwarding functions),它們看起來像下面這樣:

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

因此,當我們調用函數tgamma(z)時,我們最終調用的是boost::math::tgamma(z, user_error_policy()),並且任何的錯誤都會被重定向到我們的錯誤處理方法:

int main()
{
   // 產生一個定義域錯誤:
   std::cout << "Result of erf_inv(-10) is: "
      << erf_inv(-10) << std::endl << std::endl;
   // 產生一個 pole error:
   std::cout << "Result of tgamma(-10) is: "
      << tgamma(-10) << std::endl << std::endl;
   // 產生一個溢出錯誤:
   std::cout << "Result of tgamma(3000) is: "
      << tgamma(3000) << std::endl << std::endl;
   // 產生一個向下溢出錯誤:
   std::cout << "Result of tgamma(-190.5) is: "
      << tgamma(-190.5) << std::endl << std::endl;
   // 不幸的是我們不能推斷產生一個 denormalised result,
   // 在這個例子中我們也不能產生一個計算錯誤
   // 因為這永遠都不會發生!
}

輸出結果為:

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

注意到一些調用是如何導致一個錯誤處理方法被調用多次的,或者調用多於一個錯誤處理方法:這是人為的結果(artefact),許多函數都使用其它的基於不同錯誤處理方法的函數來實現。例如tgamma(-190.5) 使用tgamma(190.5) - 這就導致溢出 - tgamma的反射方程(reflection formula)發現它在除以一個無限值並且產生向下溢出。


PrevUpHomeNext