Checking policies
A checking policy controls how the interval class will deal
with special cases like: empty intervals, infinite numbers, invalid
values.
For example, let's consider operator+(interval, T) . The
second argument could be an invalid value (for a floating-point number, it
is a NaN). What to do in such a case? First, we could say that the second
argument can never be an invalid number. Second, we could also say such a
situation can arise but is forbidden. Third, we could allow such values and
generate an empty interval when encountered. And there is many other
possibilities.
It is the reason why such a policy is used: there is a lot of
interesting behaviors and it would be sad to arbitrarily select one of
these.
Requirements
The checking class should satisfy the following requirement (in the form
of an interface):
/* requirements for checking policy */
struct checking
{
static T pos_inf();
static T neg_inf();
static T nan();
static bool is_nan(const T&);
static T empty_lower();
static T empty_upper();
static bool is_empty(const T&, const T&);
};
The first two functions, pos_inf and neg_inf ,
are invoked each time the library has to create the infinite bound of an
interval. For example, interval::whole computes
interval(checking::neg_inf(), checking::pos_inf()) . If
infinite values are allowed and
std::numeric_limits<T>::infinity() returns a correct
value, such a value can be used.
Next comes nan . This function is used each time a function
need to return a value of type T but is unable to compute it.
It only happens when one of the arguments of the function is invalid. For
example, if you ask what the median value of an empty interval is,
nan will be used. But please remember: lower and
upper directly return the value stocked in the interval; so,
if the interval is empty, lower will not answer
by a call to checking::nan (but will return the
same value than checking::empty_lower could return).
empty_lower and empty_upper respectively
return the lower and upper bound of the empty interval. There is no
requirements for empty_lower and empty_upper to
return the same value than checking::nan . For example, if the
type T does not have any invalid value, the
empty_ functions can return the [1;0] interval.
is_nan is used to test if a value of type T is
invalid or not. is_empty tests if the interval formed by the
two arguments is empty or not. Such tests will generally be at the
beginning of each function which involves an argument of type
T . If one of the inputs is declared invalid, the the function
will try to produce an invalid value or an input interval.
Synopsis
namespace boost {
namespace numeric {
namespace interval_lib {
template<class T>
struct checking_base;
template<class T, class Checking = checking_base<T>, class Exception = exception_create_empty<T> >
struct checking_no_empty;
template<class T, class Checking = checking_base<T> >
struct checking_no_nan;
template<class T, class Checking = checking_base<T>, class Exception = exception_invalid_number<T> >
struct checking_catch_nan;
template<class T> struct exception_create_empty { T operator()(); };
template<class T> struct exception_invalid_number { void operator()(); };
} // namespace numeric
} // namespace interval_lib
} // namespace boost
Predefined classes
In order to simplify the customization of the policy, some templates are
already defined in the library.
First of all, there is checking_base . Thanks to the
information provided by std::numeric_limits<T> , this
class is able to generate a base for the policy. If T has
quiet NaNs (as said by numeric_limits::has_quiet_NaN ), then
the value is used for nan , empty_lower ,
empty_upper ; and a basic test is used for is_nan
(it is x!=x ). If T does not have quiet NaNs, then
nan is an assert(false) , the empty interval is
[1,0], and is_nan always return false . As for
nan , pos_inf returns
numeric_limits::infinity() if possible, or is an
assert(false ) otherwise. neg_inf returns the
opposite. Finally, is_empty(T l,T u) is always defined by
!(l<=u) .
Next comes checking_no_empty . Using it means that each time
an empty interval should be produced (by empty_lower and
empty_upper ), the function object given by the
Exception argument of the template is invoked and the value it
returns is propagated. So, if Exception is appropriately
defined (for example it could throw an exception, hence the name of the
argument), you can be sure no empty interval will ever be created. So
is_empty will always return false (since there is
no need to test for an empty interval). And as explained before, in that
case we can also replace nan by an assert(false) ;
you will be sure no invalid number will ever be produced. If this template
is not used, it implicitly means that all the functions can produce empty
intervals and they correctly deal with empty interval arguments.
Finally there are checking_no_nan and
checking_catch_nan . The first one expresses the functions of
the library will never get an invalid number as argument. So
is_nan will only return false . The other one
means the arguments can be an invalid number but in that case,
is_nan will call the function object Exception
and return false . Indeed, this template means invalid numbers
should never make their way through to the body of the function. If none of
this two templates is used, it implicitly means that all the functions can
get invalid number arguments and they will correctly deal with them.
exception_create_empty throws
std::runtime_error with the message "boost::interval:
empty interval created" and exception_invalid_number
throws std::invalid_argument with the message
"boost::interval: invalid number" .
Customizing your own checking policy
In order to define a suitable policy, you need to correctly say what you
expect from your interval class. First of all, are you interested in
getting empty intervals at the end of a calculus? If you do not want to
obtain empty intervals, empty_lower and
empty_upper have to fail when invoked (they can throw an
exception, set a flag, etc). However, if no function is able to produce an
empty interval, it is no more necessary to do the test, so
is_empty may always return false . In this case, a
good compiler will do a lot of optimizations.
You could also be interested in getting empty intervals at the end of
the calculus. For example, if you need to transform an array of unsure
values (or intervals) in a new array of intervals, you may not want to stop
the conversion at the first encountered problem. So
empty_lower and empty_upper need to return
suitable values in order to define an empty interval (you can use an upper
bound which is not greater or equal than the lower bound for example); and
is_empty must be able to distinguish empty intervals from the
valid intervals.
Another important question is: is it possible that some base numbers
(objects of type T ) are invalid? And if it is possible, are
they allowed or not ? If it is not possible, no test is necessary;
is_nan may always return false . In this case too,
a good compiler will do a lot of optimizations. If function arguments can
hold invalid numbers, two cases must be considered according to whether
they are allowed or not. If they are allowed, is_nan just has
to test if they are invalid or not. If they are forbidden,
is_nan should fail (exception, assert, etc.) when invoked on
an invalid argument and return false otherwise. The value
returned by nan does not have any interest since the interval
functions are guaranteed not to produce invalid interval bounds unless the
user passes invalid numbers to the constructors. So you can put an assert
inside if you do not trust the library. :-)
And finally, you need to decide what to do with nan if it
has not already been decided at the beginning, and with
pos_inf and neg_inf . These functions should
return a value or start an exceptional behavior (especially if the base
type does not have corresponding values).
Some examples
- If you need a checking policy that allows the library to correctly
manipulate data, even if they contain invalid numbers and empty
intervals, then
checking_base<T> is a
possibility.
- If you do not want empty intervals to be created and are not sure all
the numbers are valid, then
checking_catch_nan<T,
checking_no_empty<T> > can help you.
- If all the numbers will be valid and if no empty interval is supposed
to be created (or if you do not want them to be created), then you can
use
checking_no_nan<T, checking_no_empty<T> > .
Please note that if T does not have a way to represent
invalid numbers, then this policy will behave the same way as
checking_no_empty<T> . This is the default policy and
it is also called interval_lib::checking_strict .
- If all numerical data are valid but the algorithm can produce and
manipulate empty intervals, then
checking_no_nan<T>
should be used.
- Similarly, if invalid data have to be signaled and the algorithm can
manipulate empty intervals, the
checking_catch_nan<T>
is a solution.
- If you do not mind having undefined results when an empty interval or
an interval number is produced, your best bet is to create your own
policy by overloading
checking_base and modifying
is_nan et is_empty in order for them to always
return false . It is probably the fastest checking policy
available; however, it suffers from its deficient security.
Revised
2006-12-24
Copyright © 2002 Guillaume Melquiond, Sylvain Pion, Hervé
Brönnimann, Polytechnic University
Copyright © 2003-2004 Guillaume Melquiond
Distributed under the Boost Software License, Version 1.0. (See
accompanying file LICENSE_1_0.txt
or copy at http://www.boost.org/LICENSE_1_0.txt)
|