From 4a8c7c31d8367a242f25e9cc4e2d7d2d6d39327a Mon Sep 17 00:00:00 2001 From: Robert Ramey Date: Tue, 30 May 2017 20:46:45 -0700 Subject: [PATCH] passes all tests. version 2.0 of exception policy design. --- examples/example82.cpp | 2 +- examples/example83.cpp | 6 +- include/checked.hpp | 76 ++--- include/checked_result.hpp | 36 ++- include/exception.hpp | 229 ++++++++----- include/exception_policies.hpp | 162 +++++++--- include/interval.hpp | 14 +- include/safe_base_operations.hpp | 537 +++++++++++++++++-------------- test/test_checked_result.cpp | 2 +- test/test_left_shift.hpp | 4 +- 10 files changed, 640 insertions(+), 428 deletions(-) diff --git a/examples/example82.cpp b/examples/example82.cpp index 2fb1156..29dacee 100644 --- a/examples/example82.cpp +++ b/examples/example82.cpp @@ -8,7 +8,7 @@ using safe_t = boost::numeric::safe< int, boost::numeric::automatic, // note use of "automatic" policy!!! - boost::numeric::no_exceptions_policy + boost::numeric::loose_trap_policy >; int main(int argc, const char * argv[]){ diff --git a/examples/example83.cpp b/examples/example83.cpp index 55339fc..3337175 100644 --- a/examples/example83.cpp +++ b/examples/example83.cpp @@ -16,7 +16,7 @@ using safe_t = safe_signed_range< -24, 82, native, // C++ type promotion rules work OK for this example - no_exceptions_policy // catch problems at compile time + loose_trap_policy // catch problems at compile time >; int main(int argc, const char * argv[]){ @@ -25,8 +25,8 @@ int main(int argc, const char * argv[]){ // since the sum of x and y wouldn't be in the legal // range for z. // const safe_signed_literal<20> x; - const safe_signed_literal<10, native, no_exceptions_policy> x; // no problem - const safe_signed_literal<67, native, no_exceptions_policy> y; + const safe_signed_literal<10, native, loose_trap_policy> x; // no problem + const safe_signed_literal<67, native, loose_trap_policy> y; const safe_t z = x + y; std::cout << "x = " << safe_format(x) << std::endl; diff --git a/include/checked.hpp b/include/checked.hpp index 2af06ed..129c256 100644 --- a/include/checked.hpp +++ b/include/checked.hpp @@ -24,6 +24,7 @@ #include "safe_common.hpp" #include "checked_result.hpp" #include "utility.hpp" +#include "exception.hpp" namespace boost { namespace numeric { @@ -53,13 +54,13 @@ namespace detail { return t > std::numeric_limits::max() ? checked_result( - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "converted signed value too large" ) : t < std::numeric_limits::min() ? checked_result( - exception_type::negative_overflow_error, + safe_numerics_error::negative_overflow_error, "converted signed value too small" ) : @@ -76,7 +77,7 @@ namespace detail { return t > std::numeric_limits::max() ? checked_result( - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "converted unsigned value too large" ) : @@ -93,7 +94,7 @@ namespace detail { return t > std::numeric_limits::max() ? checked_result( - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "converted unsigned value too large" ) : @@ -108,13 +109,13 @@ namespace detail { return t < 0 ? checked_result( - exception_type::domain_error, + safe_numerics_error::domain_error, "converted negative value to unsigned" ) : t > std::numeric_limits::max() ? checked_result( - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "converted signed value too large" ) : @@ -138,7 +139,7 @@ cast( // conversions to integer types // from floating point types are never OK checked_result( - exception_type::domain_error, + safe_numerics_error::domain_error, "conversion of integer to float loses precision" ) : @@ -184,7 +185,7 @@ namespace detail { // INT30-C. Ensure that unsigned integer operations do not wrap std::numeric_limits::max() - u < t ? checked_result( - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "addition result too large" ) : @@ -207,13 +208,13 @@ namespace detail { // INT32-C. Ensure that operations on signed integers do not result in overflow ((u > 0) && (t > (std::numeric_limits::max() - u))) ? checked_result( - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "addition result too large" ) : ((u < 0) && (t < (std::numeric_limits::min() - u))) ? checked_result( - exception_type::negative_overflow_error, + safe_numerics_error::negative_overflow_error, "addition result too low" ) : @@ -270,7 +271,7 @@ namespace detail { return t < u ? checked_result( - exception_type::range_error, + safe_numerics_error::range_error, "subtraction result cannot be negative" ) : @@ -293,13 +294,13 @@ namespace detail { // INT32-C. Ensure that operations on signed integers do not result in overflow ((u > 0) && (t < (std::numeric_limits::min() + u))) ? checked_result( - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "subtraction result overflows result type" ) : ((u < 0) && (t > (std::numeric_limits::max() + u))) ? checked_result( - exception_type::negative_overflow_error, + safe_numerics_error::negative_overflow_error, "subtraction result overflows result type" ) : @@ -360,7 +361,7 @@ namespace detail { static_cast(t) * static_cast(u) > std::numeric_limits::max() ? checked_result( - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "multiplication overflow" ) : @@ -381,7 +382,7 @@ namespace detail { return u > 0 && t > std::numeric_limits::max() / u ? checked_result( - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "multiplication overflow" ) : @@ -410,7 +411,7 @@ namespace detail { > static_cast(std::numeric_limits::max()) ) ? checked_result( - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "multiplication overflow" ) : @@ -419,7 +420,7 @@ namespace detail { < static_cast(std::numeric_limits::min()) ) ? checked_result( - exception_type::negative_overflow_error, + safe_numerics_error::negative_overflow_error, "multiplication overflow" ) : @@ -440,7 +441,7 @@ namespace detail { u > 0 ? t > std::numeric_limits::max() / u ? checked_result( - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "multiplication overflow" ) : @@ -448,7 +449,7 @@ namespace detail { : // u <= 0 u < std::numeric_limits::min() / t ? checked_result( - exception_type::negative_overflow_error, + safe_numerics_error::negative_overflow_error, "multiplication overflow" ) : @@ -457,7 +458,7 @@ namespace detail { u > 0 ? t < std::numeric_limits::min() / u ? checked_result( - exception_type::negative_overflow_error, + safe_numerics_error::negative_overflow_error, "multiplication overflow" ) : @@ -465,7 +466,7 @@ namespace detail { : // u <= 0 t != 0 && u < std::numeric_limits::max() / t ? checked_result( - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "multiplication overflow" ) : @@ -518,7 +519,7 @@ namespace detail { return (u == -1 && t == std::numeric_limits::min()) ? checked_result( - exception_type::range_error, + safe_numerics_error::range_error, "result cannot be represented" ) : @@ -536,7 +537,7 @@ constexpr divide( ) noexcept { if(u == 0){ return checked_result( - exception_type::domain_error, + safe_numerics_error::domain_error, "divide by zero" ); } @@ -545,7 +546,7 @@ constexpr divide( if(tx.exception() || ux.exception()) return checked_result( - exception_type::domain_error, + safe_numerics_error::domain_error, "failure converting argument types" ); return detail::divide(tx, ux); @@ -574,7 +575,7 @@ constexpr modulus( static_assert(std::is_fundamental::value, "only intrinsic types permitted"); if(0 == u) return checked_result( - exception_type::domain_error, + safe_numerics_error::domain_error, "denominator is zero" ); @@ -640,7 +641,7 @@ constexpr checked_left_shift( if(u > std::numeric_limits::digits - utility::significant_bits(t)){ // behavior is undefined return checked_result( - exception_type::undefined_behavior, + safe_numerics_error::undefined_behavior, "shifting more bits than available is undefined behavior" ); } @@ -671,7 +672,7 @@ constexpr checked_left_shift( } // otherwise, the behavior is undefined. return checked_result( - exception_type::undefined_behavior, + safe_numerics_error::undefined_behavior, "shifting a negative value is undefined behavior" ); } @@ -689,14 +690,14 @@ constexpr checked_result left_shift( // if the right operand is negative if(u < 0){ return checked_result( - exception_type::implementation_defined_behavior, + safe_numerics_error::implementation_defined_behavior, "shifting negative amount is undefined behavior" ); } if(u > std::numeric_limits::digits){ // behavior is undefined return checked_result( - exception_type::implementation_defined_behavior, + safe_numerics_error::implementation_defined_behavior, "shifting more bits than available is undefined behavior" ); } @@ -742,7 +743,7 @@ constexpr checked_right_shift( // note that the C++ standard considers this case is "implemenation // defined" rather than "undefined". return checked_result( - exception_type::implementation_defined_behavior, + safe_numerics_error::implementation_defined_behavior, "shifting a negative value is undefined behavior" ); } @@ -765,14 +766,14 @@ constexpr checked_result right_shift( // if the right operand is negative if(u < 0){ return checked_result( - exception_type::implementation_defined_behavior, + safe_numerics_error::implementation_defined_behavior, "shifting negative amount is undefined behavior" ); } if(u > std::numeric_limits::digits){ // behavior is undefined return checked_result( - exception_type::implementation_defined_behavior, + safe_numerics_error::implementation_defined_behavior, "shifting more bits than available is undefined behavior" ); } @@ -799,7 +800,7 @@ constexpr checked_result bitwise_or( if(result_size > bits_type::value){ return checked_result{ - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "result type too small to hold bitwise or" }; } @@ -817,11 +818,11 @@ constexpr checked_result bitwise_xor( if(result_size > bits_type::value){ return checked_result{ - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "result type too small to hold bitwise or" }; } - +/* const checked_result rt = cast(t); if(! rt.no_exception()) return rt; @@ -829,8 +830,9 @@ constexpr checked_result bitwise_xor( const checked_result ru = cast(u); if(! ru.no_exception()) return ru; - return static_cast(ru) ^ static_cast(rt); +*/ + return t ^ u; } template @@ -844,7 +846,7 @@ constexpr checked_result bitwise_and( if(result_size > bits_type::value){ return checked_result{ - exception_type::positive_overflow_error, + safe_numerics_error::positive_overflow_error, "result type too small to hold bitwise or" }; } diff --git a/include/checked_result.hpp b/include/checked_result.hpp index 037ecb4..008b0fa 100644 --- a/include/checked_result.hpp +++ b/include/checked_result.hpp @@ -27,7 +27,7 @@ namespace numeric { template struct checked_result { - exception_type m_e; + safe_numerics_error m_e; union { R m_r; char const * m_msg; @@ -39,19 +39,30 @@ struct checked_result { checked_result() = delete; constexpr /*explicit*/ checked_result(const R & r) : - m_e(exception_type::no_exception), + m_e(safe_numerics_error::success), m_r(r) {} constexpr /*explicit*/ checked_result( - exception_type e, + safe_numerics_error e, const char * msg ) : m_e(e), m_msg(msg) {} + template + constexpr /*explicit*/ checked_result(const checked_result & t) : + m_e(t.m_e), + m_r(t.m_r) + { + if(t.no_exception()) + m_r = t.m_r; + else + m_msg = t.m_msg; + } // accesors constexpr operator R() const { + assert(no_exception()); return m_r; } @@ -95,37 +106,41 @@ struct checked_result { return ! operator!=(t); } constexpr bool no_exception() const { - return m_e == exception_type::no_exception; + return m_e == safe_numerics_error::success; } constexpr bool exception() const { - return m_e != exception_type::no_exception; + return m_e != safe_numerics_error::success; } }; template -constexpr bool operator==(const checked_result &lhs, const exception_type & rhs){ +constexpr bool operator==(const checked_result &lhs, const safe_numerics_error & rhs){ return (! lhs.exception()) ? false : lhs.m_e == rhs; } template -constexpr bool operator==(const exception_type & lhs, const checked_result &rhs){ +constexpr bool operator==(const safe_numerics_error & lhs, const checked_result &rhs){ return (! rhs.exception()) ? false : lhs = rhs.m_e; } template -constexpr bool operator!=(const checked_result &lhs, const exception_type & rhs){ +constexpr bool operator!=(const checked_result &lhs, const safe_numerics_error & rhs){ return ! (lhs == rhs); } template -constexpr bool operator!=(const exception_type & lhs, const checked_result &rhs){ +constexpr bool operator!=(const safe_numerics_error & lhs, const checked_result &rhs){ return ! (lhs == rhs); } +// invoke error handling template constexpr void dispatch(const checked_result & cr){ + // if the result contains an error condition if(cr.exception()) + // dispatch to the appropriate function dispatch(cr.m_e, cr.m_msg); + // otherwise just do a simple return } - +/* // C++ does not (yet) permit constexpr lambdas. So create some // constexpr predicates to be used by constexpr algorthms. template @@ -136,6 +151,7 @@ template constexpr bool exception(const checked_result & cr){ return ! cr.no_exception(); } +*/ } // numeric } // boost diff --git a/include/exception.hpp b/include/exception.hpp index e401f75..4e98048 100644 --- a/include/exception.hpp +++ b/include/exception.hpp @@ -15,19 +15,24 @@ // contains error indicators for results of doing checked // arithmetic on native C++ types -#include #include +#include // error_code, system_error +#include -#include "exception_policies.hpp" -#include "concept/exception_policy.hpp" +// Using the system_error code facility. This facility is more complex +// than meets the eye. To fully understand what out intent here is, +// review http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-5.html +// "Giving context-specific meaning to generic error codes" namespace boost { namespace numeric { +// errors codes for safe numerics + // in spite of the similarity, this list is distinct from the exceptions // listed in documentation for std::exception. -enum class exception_type { - no_exception, +enum class safe_numerics_error { + success = 0, positive_overflow_error, negative_overflow_error, underflow_error, @@ -35,98 +40,152 @@ enum class exception_type { domain_error, implementation_defined_behavior, undefined_behavior, - uninitialized + uninitialized_value }; -enum class exception_group { - no_exception, +} // numeric +} // boost + +namespace std { + template <> + struct is_error_code_enum + : public true_type {}; +}; + +namespace boost { +namespace numeric { + +const class : public std::error_category { +public: + virtual const char* name() const noexcept{ + return "safe numerics error"; + } + virtual std::string message(int ev) const { + switch(static_cast(ev)){ + case safe_numerics_error::success: + return "success"; + case safe_numerics_error::positive_overflow_error: + return "positive overflow error"; + case safe_numerics_error::negative_overflow_error: + return "negative overflow error"; + case safe_numerics_error::underflow_error: + return "underflow error"; + case safe_numerics_error::range_error: + return "range error"; + case safe_numerics_error::domain_error: + return "domain error"; + case safe_numerics_error::implementation_defined_behavior: + return "implementation defined behavior"; + case safe_numerics_error::undefined_behavior: + return "undefined behavior"; + case safe_numerics_error::uninitialized_value: + return "uninitialized value"; + default: + assert(false); + } + } +} safe_numerics_error_category ; + +std::error_code make_error_code(safe_numerics_error e){ + return std::error_code(static_cast(e), safe_numerics_error_category); +} + +// actions for error_codes for safe numerics. I've leveraged on +// error_condition in order to do this. I'm not sure this is a good +// idea or not. + +enum safe_numerics_actions { + no_action = 0, uninitialized_value, arithmetic_error, implementation_defined_behavior, undefined_behavior }; -// translate exception type to exception handler type -constexpr exception_group -group(const exception_type & et){ - // we can't use standard algorithms since we want this to be constexpr - // this brute force solution is simple and pretty fast anyway - switch(et){ - case exception_type::negative_overflow_error: - case exception_type::underflow_error: - case exception_type::range_error: - case exception_type::domain_error: - case exception_type::positive_overflow_error: - return exception_group::arithmetic_error; - case exception_type::undefined_behavior: - return exception_group::undefined_behavior; - case exception_type::implementation_defined_behavior: - return exception_group::implementation_defined_behavior; - case exception_type::uninitialized: - return exception_group::uninitialized_value; - case exception_type::no_exception: - return exception_group::no_exception; - } -} +} // numeric +} // boost -template -constexpr exception_handler -handler(const exception_group & eg){ - // we can't use standard algorithms since we want this to be constexpr - // this brute force solution is simple and pretty fast anyway - switch(eg){ - case exception_group::no_exception: - return exception_handler::ignore_exception; - case exception_group::uninitialized_value: - return EP::on_uninitialized_value(); - case exception_group::arithmetic_error: - return EP::on_arithmetic_error(); - case exception_group::implementation_defined_behavior: - return EP::on_implementation_defined_behavior(); - case exception_group::undefined_behavior: - return EP::on_undefined_behavior(); - default: - assert(false); - } -} - -constexpr void -throw_exception(const exception_type & e, char const * const & msg){ - switch(e){ - case exception_type::positive_overflow_error: - case exception_type::negative_overflow_error: - throw std::overflow_error(msg); - case exception_type::underflow_error: - throw std::underflow_error(msg); - case exception_type::range_error: - throw std::range_error(msg); - case exception_type::domain_error: - throw std::domain_error(msg); - case exception_type::implementation_defined_behavior: - case exception_type::undefined_behavior: - case exception_type::uninitialized: - case exception_type::no_exception: - default: - assert(false); - } +namespace std { + template <> + struct is_error_condition_enum + : public true_type {}; }; -template -constexpr void -dispatch(const exception_type & e, char const * const & msg){ - const exception_group eg = group(e); - const exception_handler eh = handler(eg); - switch(eh){ - case exception_handler::ignore_exception: - return; - case exception_handler::throw_exception: - throw_exception(e, msg); - case exception_handler::trap_exception: - assert(false); - default: - assert(false); - } +namespace boost { +namespace numeric { +const class : public std::error_category { +public: + virtual const char* name() const noexcept { + return "safe numerics error group"; + } + virtual std::string message(int ev) const { + return "safe numerics error group"; + } + // return true if a given error code corresponds to a + // given safe numeric action + virtual bool equivalent( + const std::error_code & code, + int condition + ) const noexcept { + if(code.category() != safe_numerics_error_category) + return false; + switch (condition){ + case safe_numerics_actions::no_action: + return code == safe_numerics_error::success; + case safe_numerics_actions::uninitialized_value: + return code == safe_numerics_error::uninitialized_value; + case safe_numerics_actions::arithmetic_error: + return code == safe_numerics_error::positive_overflow_error + || code == safe_numerics_error::negative_overflow_error + || code == safe_numerics_error::underflow_error + || code == safe_numerics_error::range_error + || code == safe_numerics_error::domain_error; + case safe_numerics_actions::implementation_defined_behavior: + return code == safe_numerics_error::implementation_defined_behavior; + case safe_numerics_actions::undefined_behavior: + return code == safe_numerics_error::undefined_behavior; + default: + assert(false); + } + } +} safe_numerics_actions_category ; + +std::error_condition +make_error_condition(safe_numerics_actions e){ + return std::error_condition( + static_cast(e), + safe_numerics_actions_category + ); +} + +// given an error code - return the action code which it corresponds to. +constexpr safe_numerics_actions +make_safe_numerics_action(const safe_numerics_error & e){ + // we can't use standard algorithms since we want this to be constexpr + // this brute force solution is simple and pretty fast anyway + switch(e){ + case safe_numerics_error::negative_overflow_error: + case safe_numerics_error::underflow_error: + case safe_numerics_error::range_error: + case safe_numerics_error::domain_error: + case safe_numerics_error::positive_overflow_error: + return safe_numerics_actions::arithmetic_error; + case safe_numerics_error::undefined_behavior: + return safe_numerics_actions::undefined_behavior; + case safe_numerics_error::implementation_defined_behavior: + return safe_numerics_actions::implementation_defined_behavior; + case safe_numerics_error::uninitialized_value: + return safe_numerics_actions::uninitialized_value; + case safe_numerics_error::success: + return safe_numerics_actions::no_action; + } +} + +// given an error code - return the action code which it corresponds to. +std::error_condition +make_error_condition(safe_numerics_error e){ + return std::error_condition(make_safe_numerics_action(e)); } } // numeric diff --git a/include/exception_policies.hpp b/include/exception_policies.hpp index 72987bd..05cdd2e 100644 --- a/include/exception_policies.hpp +++ b/include/exception_policies.hpp @@ -12,76 +12,142 @@ // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#include #include // is_convertible -//#include "exception.hpp" +#include "exception.hpp" namespace boost { namespace numeric { -enum class exception_handler { - ignore_exception, - throw_exception, - trap_exception -}; template< - exception_handler UV, - exception_handler AE, - exception_handler IDB, - exception_handler UB + typename AE, + typename IDB, + typename UB, + typename UV > struct exception_policy { - static exception_handler on_uninitialized_value(){ - return UV; + static constexpr void on_arithmetic_error( + const safe_numerics_error & e, + const char * msg + ){ + AE(e, msg); } - static exception_handler on_arithmetic_error(){ - return AE; + static constexpr void on_implementation_defined_behavior( + const safe_numerics_error & e, + const char * msg + ){ + IDB(e, msg); } - static exception_handler on_implementation_defined_behavior(){ - return IDB; + static constexpr void on_undefined_behavior( + const safe_numerics_error & e, + const char * msg + ){ + UB(e, msg); } - static exception_handler on_undefined_behavior(){ - return UB; + static constexpr void on_uninitialized_value( + const safe_numerics_error & e, + const char * msg + ){ + UV(e, msg); } }; -// normal policy - permit runtime exceptions -using default_exception_policy = exception_policy< - exception_handler::ignore_exception, - exception_handler::throw_exception, - exception_handler::ignore_exception, - exception_handler::trap_exception +//////////////////////////////////////////////////////////////////////////////// +// pre-made error action handers + +// ignore any error and just return. +struct ignore_exception { + constexpr ignore_exception(const safe_numerics_error & e, const char * msg){} +}; + +// If an exceptional condition is detected at runtime throw the exception. +struct throw_exception { + throw_exception(const safe_numerics_error & e, const char * message){ + throw std::system_error(std::error_code(e), message); + } +}; + +// emit compile time error if this is invoked. +struct trap_exception {}; + +//////////////////////////////////////////////////////////////////////////////// +// pre-made error policy classes + +// loose policy +// - throw on arithmetic errors +// - ignore other errors. +// Some applications ignore these issues and still work and we don't +// want to update them. +using loose_exception_policy = exception_policy< + throw_exception, // arithmetic error + ignore_exception, // implementation defined behavior + ignore_exception, // undefined behavior + ignore_exception // uninitialized value >; -// trap non portable C++ code at compile time but permit -// exceptions to be thrown for runtime errors +// loose trap +// same as above in that it doesn't check for various undefined behaviors +// but traps at compile time for hard arithmetic errors. This policy +// would be suitable for older embedded systems which depend on +// bit manipulation operations to work. +using loose_trap_policy = exception_policy< + trap_exception, // arithmetic error + ignore_exception, // implementation defined behavior + ignore_exception, // undefined behavior + ignore_exception // uninitialized value +>; + +// strict exception policy +// - permit just about anything +// - throw at runtime on any kind of error +// recommended for new code. Check everything at compile time +// if possible and runtime if necessary. Trap or Throw as +// appropriate. Should guarentee code to be portable accross +// archtectures. using strict_exception_policy = exception_policy< - exception_handler::ignore_exception, - exception_handler::throw_exception, - exception_handler::trap_exception, - exception_handler::trap_exception + throw_exception, + throw_exception, + throw_exception, + throw_exception >; -// use when any possible exceptions should be trapped at compile time -// but we're a little loose on things like bit shifts etc. -// this might be attractive choice for small embedded systems -using no_exceptions_policy = exception_policy< - exception_handler::ignore_exception, - exception_handler::trap_exception, - exception_handler::ignore_exception, - exception_handler::trap_exception +// strict trap +// same as above but requires code to be written in such a way as +// to make exceptions impossible +using strict_trap_policy = exception_policy< + trap_exception, + trap_exception, + trap_exception, + trap_exception >; -// use when any possible exceptions should be trapped at compile time -// but we're a little loose on things like bit shifts etc. -// this might be attractive choice for small embedded systems -using strict_no_exceptions_policy = exception_policy< - exception_handler::ignore_exception, - exception_handler::trap_exception, - exception_handler::trap_exception, - exception_handler::trap_exception ->; +// default policy +// One would use this first. After experimentation, one might +// replace some actions with ignore_exception +using default_exception_policy = strict_exception_policy; + +template +constexpr void +dispatch(const safe_numerics_error & e, char const * const & msg){ + const safe_numerics_actions a = make_safe_numerics_action(e); + switch(a){ + case safe_numerics_actions::uninitialized_value: + EP::on_uninitialized_value(e, msg); + break; + case safe_numerics_actions::arithmetic_error: + EP::on_arithmetic_error(e, msg); + break; + case safe_numerics_actions::implementation_defined_behavior: + EP::on_implementation_defined_behavior(e, msg); + break; + case safe_numerics_actions::undefined_behavior: + EP::on_undefined_behavior(e, msg); + break; + default: + assert(false); + } +} + } // namespace numeric } // namespace boost diff --git a/include/interval.hpp b/include/interval.hpp index 0676a37..b072e54 100644 --- a/include/interval.hpp +++ b/include/interval.hpp @@ -108,8 +108,8 @@ template constexpr interval minmax(const std::initializer_list & l){ using namespace boost::numeric; - T minimum{exception_type::positive_overflow_error, ""}; - T maximum{exception_type::negative_overflow_error, ""}; + T minimum{safe_numerics_error::positive_overflow_error, ""}; + T maximum{safe_numerics_error::negative_overflow_error, ""}; // note: we can't use for_each and a lambda because neither of these are // constexpr for( @@ -119,9 +119,9 @@ minmax(const std::initializer_list & l){ ){ // std::cout << *i << ','; // std::cout.flush(); - if(minimum != exception_type::negative_overflow_error){ + if(minimum != safe_numerics_error::negative_overflow_error){ // if it corresponds to the lowest value - if(*i == exception_type::negative_overflow_error){ + if(*i == safe_numerics_error::negative_overflow_error){ // initialize the minimum minimum = *i; } @@ -134,9 +134,9 @@ minmax(const std::initializer_list & l){ } } } - if(maximum != exception_type::positive_overflow_error){ + if(maximum != safe_numerics_error::positive_overflow_error){ // if it corresponds to the highest value - if(*i == exception_type::positive_overflow_error){ + if(*i == safe_numerics_error::positive_overflow_error){ // initialize the maximum maximum = *i; } @@ -361,7 +361,7 @@ constexpr interval> intersection( if(rl > ru){ return checked_result>( - exception_type::uninitialized, + safe_numerics_error::uninitialized_value, "null intersection" ); } diff --git a/include/safe_base_operations.hpp b/include/safe_base_operations.hpp index 9e237cd..6f7b1a7 100644 --- a/include/safe_base_operations.hpp +++ b/include/safe_base_operations.hpp @@ -49,7 +49,7 @@ struct validate_detail { checked::cast(t) : checked_result( - exception_type::domain_error, + safe_numerics_error::domain_error, "Value out of range for this safe type" ) ; @@ -251,6 +251,7 @@ struct common_promotion_policy { template struct addition_result { +private: using promotion_policy = typename common_promotion_policy::type; using result_base_type = typename promotion_policy::template addition_result::type; @@ -269,14 +270,12 @@ struct addition_result { constexpr static const interval> r_interval = add(t_interval, u_interval); - constexpr static const bool exception_possible() { return r_interval.l.exception() || r_interval.u.exception(); } using exception_policy = typename common_exception_policy::type; - struct safe_type { using type = safe_base< result_base_type, @@ -294,22 +293,32 @@ struct addition_result { } }; - struct unsafe_type { using type = result_base_type; constexpr static const type make(const result_base_type & t){ return t; } }; + using type_helper = typename boost::mpl::if_c< std::numeric_limits::is_integer, safe_type, unsafe_type >::type; - using type = typename type_helper::type; - constexpr static const type make(const result_base_type & t){ - return type_helper::make(t); + constexpr static result_base_type + add(const T & t, const U & u){ + return + static_cast(base_value(t)) + + static_cast(base_value(u)) + ; + } + + // exception not possible + constexpr static result_base_type + return_value(const T & t, const U & u, std::false_type){ + static_assert(! exception_possible(), "no runtime check"); + return add(t,u); } // exception possible @@ -320,18 +329,24 @@ struct addition_result { base_value(t), base_value(u) ); + if(!r.exception()) + return static_cast(r); + // handle error condition dispatch(r); - return static_cast(r); + // if we get here, the error has been ignored + // just get the result the old fashioned way + return add(t, u); } - // exception not possible - constexpr static result_base_type - return_value(const T & t, const U & u, std::false_type){ - static_assert(! exception_possible(), "no runtime check"); - return - static_cast(base_value(t)) - + static_cast(base_value(u)) - ; +public: + using type = typename type_helper::type; + + constexpr static type return_value(const T & t, const U & u){ + return type_helper::make( return_value( + t, + u, + typename std::integral_constant() + )); } }; @@ -343,14 +358,7 @@ typename std::enable_if< typename addition_result::type >::type constexpr operator+(const T & t, const U & u){ - using ar = addition_result; - return ar::make( - ar::return_value( - t, - u, - typename std::integral_constant() - ) - ); + return addition_result::return_value(t, u); } template @@ -370,6 +378,7 @@ constexpr operator+=(T & t, const U & u){ template struct subtraction_result { +private: using promotion_policy = typename common_promotion_policy::type; using result_base_type = typename promotion_policy::template subtraction_result::type; @@ -422,10 +431,20 @@ struct subtraction_result { safe_type, unsafe_type >::type; - using type = typename type_helper::type; - constexpr static const type make(const result_base_type & t){ - return type_helper::make(t); + constexpr static result_base_type + subtract(const T & t, const U & u){ + return + static_cast(base_value(t)) + - static_cast(base_value(u)) + ; + } + + // exception not possible + constexpr static result_base_type + return_value(const T & t, const U & u, std::false_type){ + static_assert(! exception_possible(), "no runtime check"); + return subtract(t, u); } // exception possible @@ -436,18 +455,20 @@ struct subtraction_result { base_value(t), base_value(u) ); + if(!r.exception()) + return static_cast(r); dispatch(r); - return static_cast(r); + return subtract(t, u); } +public: + using type = typename type_helper::type; - // exception not possible - constexpr static result_base_type - return_value(const T & t, const U & u, std::false_type){ - static_assert(! exception_possible(), "no runtime check"); - return - static_cast(base_value(t)) - - static_cast(base_value(u)) - ; + constexpr static type return_value(const T & t, const U & u){ + return type_helper::make( return_value( + t, + u, + typename std::integral_constant() + )); } }; @@ -459,14 +480,7 @@ typename std::enable_if< typename subtraction_result::type >::type constexpr operator-(const T & t, const U & u){ - using sr = subtraction_result; - return sr::make( - sr::return_value( - t, - u, - typename std::integral_constant() - ) - ); + return subtraction_result::return_value(t, u); } template @@ -486,6 +500,7 @@ constexpr operator-=(T & t, const U & u){ template struct multiplication_result { +private: using promotion_policy = typename common_promotion_policy::type; using result_base_type = typename promotion_policy::template multiplication_result::type; @@ -533,20 +548,32 @@ struct multiplication_result { return false; } }; + using type_helper = typename boost::mpl::if_c< std::numeric_limits::is_integer, safe_type, unsafe_type >::type; - using type = typename type_helper::type; - constexpr static const type make(const result_base_type & t){ - return type_helper::make(t); - } constexpr static bool exception_possible() { return type_helper::exception_possible(); } + constexpr static result_base_type + multiply(const T & t, const U & u){ + return + static_cast(base_value(t)) + * static_cast(base_value(u)) + ; + } + + // exception not possible + constexpr static result_base_type + return_value(const T & t, const U & u, std::false_type){ + static_assert(! exception_possible(), "no runtime check"); + return multiply(t, u); + } + // exception possible constexpr static result_base_type return_value(const T & t, const U & u, std::true_type){ @@ -555,18 +582,20 @@ struct multiplication_result { base_value(t), base_value(u) ); + if(!r.exception()) + return static_cast(r); dispatch(r); - return static_cast(r); + return multiply(t, u); } +public: + using type = typename type_helper::type; - // exception not possible - constexpr static result_base_type - return_value(const T & t, const U & u, std::false_type){ - static_assert(! exception_possible(), "no runtime check"); - return - static_cast(base_value(t)) - * static_cast(base_value(u)) - ; + constexpr static type return_value(const T & t, const U & u){ + return type_helper::make( return_value( + t, + u, + typename std::integral_constant() + )); } }; @@ -579,14 +608,7 @@ typename std::enable_if< >::type constexpr operator*(const T & t, const U & u){ // argument dependent lookup should guarentee that we only get here - using mr = multiplication_result; - return mr::make( - mr::return_value( - t, - u, - typename std::integral_constant() - ) - ); + return multiplication_result::return_value(t, u); } template @@ -607,12 +629,14 @@ constexpr operator*=(T & t, const U & u){ // key idea here - result will never be larger than T template struct division_result { +private: using promotion_policy = typename common_promotion_policy::type; - using r_base = typename promotion_policy::template division_result::type; + using result_base_type = typename promotion_policy::template division_result::type; using t_base = typename base_type::type; using u_base = typename base_type::type; using exception_policy = typename common_exception_policy::type; + struct safe_type { constexpr static const interval t_interval{ base_value(std::numeric_limits::min()), @@ -624,8 +648,8 @@ struct division_result { base_value(std::numeric_limits::max()) }; - constexpr static const interval> r_interval - = divide(t_interval, u_interval); + constexpr static const interval> r_interval + = divide(t_interval, u_interval); static_assert(! r_interval.l.exception(), "unexpected negative overflow"); static_assert(! r_interval.u.exception(), "unexpected positive overflow"); @@ -640,73 +664,86 @@ struct division_result { ; } using type = safe_base< - r_base, - static_cast(r_interval.l), - static_cast(r_interval.u), + result_base_type, + static_cast(r_interval.l), + static_cast(r_interval.u), promotion_policy, exception_policy >; - constexpr static const type make(const r_base & t){ + constexpr static type make(const result_base_type & t){ return type(t, std::false_type()); } }; struct unsafe_type { - using type = r_base; - constexpr static const type make(const r_base & t){ + using type = result_base_type; + constexpr static const type make(const result_base_type & t){ return t; } constexpr static bool exception_possible() { return false; } }; + using type_helper = typename boost::mpl::if_c< - std::numeric_limits::is_integer, + std::numeric_limits::is_integer, safe_type, unsafe_type >::type; - using type = typename type_helper::type; - constexpr static const type make(const r_base & t){ - return type_helper::make(t); - } constexpr static bool exception_possible() { return type_helper::exception_possible(); } + constexpr static result_base_type + divide(const T & t, const U & u){ + return + static_cast(base_value(t)) + / static_cast(base_value(u)) + ; + } + constexpr static int bits = std::min( std::numeric_limits::digits, std::max(std::initializer_list{ - std::numeric_limits::digits, + std::numeric_limits::digits, std::numeric_limits::digits, std::numeric_limits::digits - }) + (std::numeric_limits::is_signed ? 1 : 0) + }) + (std::numeric_limits::is_signed ? 1 : 0) ); using temp_base = typename boost::mpl::if_c< - std::numeric_limits::is_signed, + std::numeric_limits::is_signed, typename boost::int_t::least, typename boost::uint_t::least >::type; + // exception not possible + constexpr static result_base_type + return_value(const T & t, const U & u, std::false_type){ + return divide(t, u); + } + // exception possible - constexpr static r_base + constexpr static result_base_type return_value(const T & t, const U & u, std::true_type){ checked_result r = checked::divide( base_value(t), base_value(u) ); - + if(!r.exception()) + return static_cast(r); dispatch(r); - return static_cast(r); + return divide(t, u); } +public: + using type = typename type_helper::type; - // exception not possible - constexpr static r_base - return_value(const T & t, const U & u, std::false_type){ - return - static_cast(base_value(t)) - / static_cast(base_value(u)) - ; + constexpr static type return_value(const T & t, const U & u){ + return type_helper::make( return_value( + t, + u, + typename std::integral_constant() + )); } }; @@ -718,14 +755,7 @@ typename std::enable_if< typename division_result::type >::type constexpr operator/(const T & t, const U & u){ - using dr = division_result; - return dr::make( - dr::return_value( - t, - u, - typename std::integral_constant() - ) - ); + return division_result::return_value(t, u); } template @@ -745,6 +775,7 @@ constexpr operator/=(T & t, const U & u){ template struct modulus_result { +private: using promotion_policy = typename common_promotion_policy::type; using result_base_type = typename promotion_policy::template modulus_result::type; @@ -801,15 +832,26 @@ struct modulus_result { safe_type, unsafe_type >::type; - using type = typename type_helper::type; - constexpr static const type make(const result_base_type & t){ - return type_helper::make(t); - } constexpr static bool exception_possible() { return type_helper::exception_possible(); } + constexpr static result_base_type + modulus(const T & t, const U & u){ + return + static_cast(base_value(t)) + % static_cast(base_value(u)) + ; + } + + // exception not possible + constexpr static result_base_type + return_value(const T & t, const U & u, std::false_type){ + static_assert(! exception_possible(), "no runtime check"); + return modulus(t, u); + } + // exception possible constexpr static result_base_type return_value(const T & t, const U & u, std::true_type){ @@ -818,18 +860,20 @@ struct modulus_result { base_value(t), base_value(u) ); + if(!r.exception()) + return static_cast(r); dispatch(r); return static_cast(r); } +public: + using type = typename type_helper::type; - // exception not possible - constexpr static result_base_type - return_value(const T & t, const U & u, std::false_type){ - static_assert(! exception_possible(), "no runtime check"); - return - static_cast(base_value(t)) - % static_cast(base_value(u)) - ; + constexpr static type return_value(const T & t, const U & u){ + return type_helper::make( return_value( + t, + u, + typename std::integral_constant() + )); } }; @@ -842,14 +886,7 @@ typename std::enable_if< >::type constexpr operator%(const T & t, const U & u){ // see https://en.wikipedia.org/wiki/Modulo_operation - using mr = modulus_result; - return mr::make( - mr::return_value( - t, - u, - typename std::integral_constant() - ) - ); + return modulus_result::return_value(t, u); } template @@ -972,6 +1009,7 @@ constexpr operator!=(const T & lhs, const U & rhs) { // left shift template struct left_shift_result { +private: using promotion_policy = typename common_promotion_policy::type; using result_base_type = typename promotion_policy::template left_shift_result::type; @@ -1001,6 +1039,36 @@ struct left_shift_result { } using exception_policy = typename common_exception_policy::type; + + constexpr static result_base_type + left_shift(const T & t, const U & u){ + return + static_cast(base_value(t)) + << static_cast(base_value(u)) + ; + } + + // exception not possible + constexpr static result_base_type + return_value(const T & t, const U & u, std::false_type){ + static_assert(! exception_possible(), "no runtime check"); + return left_shift(t, u); + } + + // exception possible + constexpr static result_base_type + return_value(const T & t, const U & u, std::true_type){ + static_assert(exception_possible(), "implement runtime check"); + checked_result r = checked::left_shift( + base_value(t), + base_value(u) + ); + if(!r.exception()) + return static_cast(r); + dispatch(r); + return left_shift(t, u); + } +public: using type = safe_base< result_base_type, r_interval.l.exception() @@ -1013,26 +1081,12 @@ struct left_shift_result { exception_policy >; - // exception possible - constexpr static result_base_type - return_value(const T & t, const U & u, std::true_type){ - static_assert(exception_possible(), "implement runtime check"); - checked_result r = checked::left_shift( - base_value(t), - base_value(u) + constexpr static type return_value(const T & t, const U & u){ + return return_value( + t, + u, + typename std::integral_constant() ); - dispatch(r); - return static_cast(r); - } - - // exception not possible - constexpr static result_base_type - return_value(const T & t, const U & u, std::false_type){ - static_assert(! exception_possible(), "no runtime check"); - return - static_cast(base_value(t)) - << static_cast(base_value(u)) - ; } }; @@ -1056,15 +1110,7 @@ constexpr operator<<(const T & t, const U & u){ static_assert( std::numeric_limits::is_integer, "shift amount must be an integer" ); - using lsr = left_shift_result; - return typename lsr::type( - lsr::return_value( - t, - u, - typename std::integral_constant() - ), - std::false_type() // don't need to revalidate - ); + return left_shift_result::return_value(t, u); } template @@ -1111,6 +1157,36 @@ struct right_shift_result { } using exception_policy = typename common_exception_policy::type; + + constexpr static result_base_type + right_shift(const T & t, const U & u){ + return + static_cast(base_value(t)) + >> static_cast(base_value(u)) + ; + } + + // exception not possible + constexpr static result_base_type + return_value(const T & t, const U & u, std::false_type){ + static_assert(! exception_possible(), "no runtime check"); + return right_shift(t, u); + } + + // exception possible + constexpr static result_base_type + return_value(const T & t, const U & u, std::true_type){ + static_assert(exception_possible(), "implement runtime check"); + checked_result r = checked::right_shift( + base_value(t), + base_value(u) + ); + if(!r.exception()) + return static_cast(r); + dispatch(r); + return right_shift(t, u); + } +public: using type = safe_base< result_base_type, r_interval.l.exception() @@ -1123,26 +1199,12 @@ struct right_shift_result { exception_policy >; - // exception possible - constexpr static result_base_type - return_value(const T & t, const U & u, std::true_type){ - static_assert(exception_possible(), "implement runtime check"); - checked_result r = checked::right_shift( - base_value(t), - base_value(u) + constexpr static type return_value(const T & t, const U & u){ + return return_value( + t, + u, + typename std::integral_constant() ); - dispatch(r); - return static_cast(r); - } - - // exception not possible - constexpr static result_base_type - return_value(const T & t, const U & u, std::false_type){ - static_assert(! exception_possible(), "no runtime check"); - return - static_cast(base_value(t)) - >> static_cast(base_value(u)) - ; } }; @@ -1165,15 +1227,7 @@ constexpr operator>>(const T & t, const U & u){ static_assert( std::numeric_limits::is_integer, "shift amount must be an integer" ); - using rsr = right_shift_result; - return typename rsr::type( - rsr::return_value( - t, - u, - typename std::integral_constant() - ), - std::false_type() // don't need to revalidate - ); + return right_shift_result::return_value(t, u); } template @@ -1194,6 +1248,7 @@ constexpr operator>>=(T & t, const U & u){ // operator | template struct bitwise_or_result { +private: using promotion_policy = typename common_promotion_policy::type; using result_base_type = typename promotion_policy::template bitwise_or_result::type; @@ -1216,6 +1271,7 @@ struct bitwise_or_result { return r_interval.l.exception() || r_interval.u.exception(); } using exception_policy = typename common_exception_policy::type; + struct safe_type { using type = safe_base< result_base_type, @@ -1243,10 +1299,19 @@ struct bitwise_or_result { safe_type, unsafe_type >::type; - using type = typename type_helper::type; - constexpr static const type make(const result_base_type & t){ - return type_helper::make(t); + constexpr static result_base_type + bitwise_or(const T & t, const U & u){ + return + static_cast(base_value(t)) + | static_cast(base_value(u)); + } + + // exception not possible + constexpr static result_base_type + return_value(const T & t, const U & u, std::false_type){ + static_assert(! exception_possible(), "no runtime check"); + return bitwise_or(t, u); } // exception possible @@ -1257,18 +1322,21 @@ struct bitwise_or_result { base_value(t), base_value(u) ); + if(!r.exception()) + return static_cast(r); dispatch(r); - return static_cast(r); + return bitwise_or(t, u); } +public: + // lazy_enable_if_c depends on this + using type = typename type_helper::type; - // exception not possible - constexpr static result_base_type - return_value(const T & t, const U & u, std::false_type){ - static_assert(! exception_possible(), "no runtime check"); - return - static_cast(base_value(t)) - | static_cast(base_value(u)) - ; + constexpr static type return_value(const T & t, const U & u){ + return type_helper::make( return_value( + t, + u, + typename std::integral_constant() + )); } }; @@ -1280,14 +1348,7 @@ typename std::enable_if< typename bitwise_or_result::type >::type constexpr operator|(const T & t, const U & u){ - using bwor = bitwise_or_result; - return bwor::make( - bwor::return_value( - t, - u, - typename std::integral_constant() - ) - ); + return bitwise_or_result::return_value(t, u); } template @@ -1354,10 +1415,19 @@ struct bitwise_and_result { safe_type, unsafe_type >::type; - using type = typename type_helper::type; - constexpr static const type make(const result_base_type & t){ - return type_helper::make(t); + constexpr static result_base_type + bitwise_and(const T & t, const U & u){ + return + static_cast(base_value(t)) + & static_cast(base_value(u)) + ; + } + // exception not possible + constexpr static result_base_type + return_value(const T & t, const U & u, std::false_type){ + static_assert(! exception_possible(), "no runtime check"); + return bitwise_and(t, u); } // exception possible @@ -1369,17 +1439,17 @@ struct bitwise_and_result { base_value(u) ); dispatch(r); - return static_cast(r); + return bitwise_and(t, u); } +public: + using type = typename type_helper::type; - // exception not possible - constexpr static result_base_type - return_value(const T & t, const U & u, std::false_type){ - static_assert(! exception_possible(), "no runtime check"); - return - static_cast(base_value(t)) - & static_cast(base_value(u)) - ; + constexpr static type return_value(const T & t, const U & u){ + return type_helper::make( return_value( + t, + u, + typename std::integral_constant() + )); } }; @@ -1391,14 +1461,7 @@ typename std::enable_if< typename bitwise_and_result::type >::type constexpr operator&(const T & t, const U & u){ - using bwar = bitwise_and_result; - return bwar::make( - bwar::return_value( - t, - u, - typename std::integral_constant() - ) - ); + return bitwise_and_result::return_value(t, u); } template @@ -1416,6 +1479,7 @@ constexpr operator&=(T & t, const U & u){ // operator ^ template struct bitwise_xor_result { +private: using promotion_policy = typename common_promotion_policy::type; using result_base_type = typename promotion_policy::template bitwise_xor_result::type; @@ -1454,6 +1518,7 @@ struct bitwise_xor_result { return type(t, std::false_type()); } }; + struct unsafe_type { using type = result_base_type; constexpr static const type make(const result_base_type & t){ @@ -1465,10 +1530,12 @@ struct bitwise_xor_result { safe_type, unsafe_type >::type; - using type = typename type_helper::type; - constexpr static const type make(const result_base_type & t){ - return type_helper::make(t); + constexpr static result_base_type + bitwise_xor(const T & t, const U & u){ + return + static_cast(base_value(t)) + ^ static_cast(base_value(u)); } // exception possible @@ -1479,18 +1546,27 @@ struct bitwise_xor_result { base_value(t), base_value(u) ); + if(!r.exception()) + return static_cast(r); dispatch(r); - return static_cast(r); + return bitwise_xor(t, u); } // exception not possible constexpr static result_base_type return_value(const T & t, const U & u, std::false_type){ static_assert(! exception_possible(), "no runtime check"); - return - static_cast(base_value(t)) - ^ static_cast(base_value(u)) - ; + return bitwise_xor(t, u); + } +public: + using type = typename type_helper::type; + + constexpr static type return_value(const T & t, const U & u){ + return type_helper::make( return_value( + t, + u, + typename std::integral_constant() + )); } }; @@ -1499,17 +1575,10 @@ typename std::enable_if< boost::numeric::is_safe::value || boost::numeric::is_safe::value , - typename bitwise_or_result::type + typename bitwise_xor_result::type >::type constexpr operator^(const T & t, const U & u){ - using bwxor = bitwise_or_result; - return bwxor::make( - bwxor::return_value( - t, - u, - typename std::integral_constant() - ) - ); + return bitwise_xor_result::return_value(t, u); } template @@ -1569,7 +1638,7 @@ void safe_base::input( validated_cast(m_t); // no need to store result if(is.fail()){ boost::numeric::dispatch( - boost::numeric::exception_type::domain_error, + boost::numeric::safe_numerics_error::domain_error, "error in file input" ); } diff --git a/test/test_checked_result.cpp b/test/test_checked_result.cpp index 9c2cf2b..37aa454 100644 --- a/test/test_checked_result.cpp +++ b/test/test_checked_result.cpp @@ -22,7 +22,7 @@ bool test2(){ using namespace boost::numeric; checked_result x1(0); assert(0 == x1); - checked_result x2(exception_type::positive_overflow_error, "exception message"); + checked_result x2(safe_numerics_error::positive_overflow_error, "exception message"); assert(! x2.no_exception()); return true; } diff --git a/test/test_left_shift.hpp b/test/test_left_shift.hpp index 9a6c74a..995d79b 100644 --- a/test/test_left_shift.hpp +++ b/test/test_left_shift.hpp @@ -38,7 +38,7 @@ bool test_left_shift( if(expected_result == 'x'){ std::cout - << "failed to detect error in left shift " + << "failed to detect arithmetic error in left shift " << std::hex << result << "(" << std::dec << result << ")" << " ! = "<< av1 << " << " << av2 << std::endl; @@ -52,7 +52,7 @@ bool test_left_shift( catch(const std::exception & e){ if(expected_result == '.'){ std::cout - << "erroneously detected error in left shift " + << "erroneously detected arithmetic error in left shift " << std::hex << result << "(" << std::dec << result << ")" << " == "<< av1 << " << " << av2 << ' ' << e.what()