In introduction, we showed an example of how overflow of adding two non-infinite float variables produce an infinite result. We reproduce the same example here for reference.
#include <iostream> #include <limits> int main(){ using namespace std; float a = numeric_limits<float>::max(); float b = a + numeric_limits<float>::max(); cout << "b is " << b << endl; //outputs: b is inf return 0; }
If we replace the type used by both variables to safe_float<float, check_addition_overflow>, the addition would have thrown an exception to inform the user.
#include <iostream> #include <limits> #include <boost/safe_float.hpp> int main(){ using namespace std; using namespace boost::safe_float; try { using sf_without_overflow = safe_float<float,policy::check_addition_overflow>; sf_without_overflow a = numeric_limits<sf_without_overflow >::max(); sf_without_overflow b = a + a; } catch { const boost::safe_float_exception& e) { cout << "safe_float required guarantees broken" << endl; } return 0; }
Here the addition throws an exception informing the user that one of the requirements was not covered by the operation result. It is important to notice safe_float does nothing to fix this, the task of the library is detect and inform only, user needs to handle the detected problems.
The minimal granularity of a checking policies is checking one operation for a single concern. They can be composed for providing more complex checks and affect more operations. The library includes several common use compositions under the convenience.hpp file and it is easy to introduce new checks for particular purposes if needed. The complete list of policies can be found in the Policies section of the documentation. The list of policies do not restrict to only flags in the FENV being checked, but also include policies to define how to safe_float plays along with other datatypes. For example, policy::allow_cast_to<T> defines if a cast to T is allowed or not.
The following is an example allowing to cast from integer to safe_float and checking for overflow in additions.
#include <iostream> #include <limits> #include <boost/safe_float.hpp> int main(){ using namespace std; using namespace boost::safe_float; using sf = safe_float<float, policy::compose<check_overflow>, policy::allow_cast_from<int> >; int i = 1; double d = 2.0; sf a{i}; //works sf b{d}; //fails compilation return 0; }
In some contexts, as real-time critical systems, the use of exceptions is restricted or fully deactivated. For this contexts, it is useful to specify other error handling methods to be used. For a full list of reported methods and how to define a custom one, please check the Concept reporter documentation.
The following is a example of how to use the library for asserting in place of throwing.
#include <iostream> #include <limits> #include <boost/safe_float.hpp> int main(){ using namespace std; using namespace boost::safe_float; using sf_asserting_overflows=safe_float<float,policy::check_overflow<reporter::on_fail_assert>>; sf_asserting_overflows a = numeric_limits<sf_asserting_overflows>::max(); sf_asserting_overflows b = a + a; return 0; }
Here, assert will be used in place of throw aborting execution and informing in debug mode, but having no impact at all in the release mode. Other alternative reporters provided include abort_on_fail, throw_on_fail, unexpected_on_fail, and log_on_fail.