...one of the most highly
regarded and expertly designed C++ library projects in the
world.

— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards

`#include <boost/multiprecision/logged_adaptor.hpp>`

namespace boost{ namespace multiprecision{ template <class Backend> void log_postfix_event(const Backend& result, const char* event_description); template <class Backend, class T> void log_postfix_event(const Backend& result1, const T& result2, const char* event_description); template <class Backend> void log_prefix_event(const Backend& arg1, const char* event_description); template <class Backend, class T> void log_prefix_event(const Backend& arg1, const T& arg2, const char* event_description); template <class Backend, class T, class U> void log_prefix_event(const Backend& arg1, const T& arg2, const U& arg3, const char* event_description); template <class Backend, class T, class U, class V> void log_prefix_event(const Backend& arg1, const T& arg2, const U& arg3, const V& arg4, const char* event_description); template <Backend> class logged_adaptor; template <class Number> using logged_adaptor_t = number<logged_adaptor<typename Number::backend_type>, Number::et>; }} // namespaces

The `logged_adaptor`

type
is used in conjunction with `number`

and some other backend type: it acts as a thin wrapper around some other
backend to class `number`

and logs all the events that take place on that object. Before any number
operation takes place, it calls `log_prefix_event`

with the arguments to the operation (up to 4), plus a string describing
the operation. Then after the operation it calls `log_postfix_event`

with the result of the operation, plus a string describing the operation.
Optionally, `log_postfix_event`

takes a second result argument: this occurs when the result of the operation
is not a `number`

, for example
when `fpclassify`

is called,
`log_postfix_event`

will
be called with `result1`

being the argument to the function, and `result2`

being the integer result of `fpclassify`

.

The default versions of `log_prefix_event`

and `log_postfix_event`

do
nothing, it is therefore up to the user to overload these for the particular
backend being observed.

This type provides `numeric_limits`

support whenever the template argument Backend does so.

Template alias `logged_adaptor_t`

can be used as a shortcut for converting some instantiation of `number<>`

to it's logged euqivalent.

This type is particularly useful when combined with an interval number
type - in this case we can use `log_postfix_event`

to monitor the error accumulated after each operation. We could either
set some kind of trap whenever the accumulated error exceeds some threshold,
or simply print out diagnostic information. Using this technique we can
quickly locate the cause of numerical instability in a particular routine.
The following example demonstrates this technique in a trivial algorithm
that deliberately introduces cancellation error:

#include <iostream> #include <iomanip> #include <boost/multiprecision/fwd.hpp> // // Begin by overloading log_postfix_event so we can capture each arithmetic event as it happens, // unfortunately this must occur BEFORE we include the full header, so just include the forward // declarations and define our overloads for now. Note that in some cases we may need to just // declare the overloads here, and define them once the types become concrete: // namespace boost { namespace multiprecision { template <unsigned D> inline void log_postfix_event(const mpfi_float_backend<D>& val, const char* event_description) { // Print out the (relative) diameter of the interval: using namespace boost::multiprecision; number<mpfr_float_backend<D> > diam; mpfi_diam(diam.backend().data(), val.data()); std::cout << "Diameter was " << diam << " after operation: " << event_description << std::endl; } template <unsigned D, class T> inline void log_postfix_event(const mpfi_float_backend<D>&, const T&, const char* event_description) { // This version is never called in this example. } } } // namespace boost::multiprecision // // Now we can include the actual multiprecision headers and make the types concrete: // #include <boost/multiprecision/mpfi.hpp> #include <boost/multiprecision/logged_adaptor.hpp> int main() { using namespace boost::multiprecision; using logged_type = logged_adaptor_t<mpfi_float_50>; // // Test case deliberately introduces cancellation error, relative size of interval // gradually gets larger after each operation: // logged_type a = 1; a /= 10; for(unsigned i = 0; i < 13; ++i) { logged_type b = a * 9; b /= 10; a -= b; } std::cout << "Final value was: " << a << std::endl; return 0; }

When we examine program output we can clearly see that the diameter of the interval increases after each subtraction:

Diameter was -0 after operation: Default construct Diameter was 0 after operation: Assignment from arithmetic type Diameter was 3.34096e-51 after operation: /= Diameter was -0 after operation: Default construct Diameter was 5.93948e-51 after operation: * Diameter was 7.42435e-51 after operation: /= Diameter was 1.00229e-49 after operation: -= Diameter was -0 after operation: Default construct Diameter was 1.00229e-49 after operation: * Diameter was 1.02085e-49 after operation: /= Diameter was 1.92105e-48 after operation: -= Diameter was -0 after operation: Default construct Diameter was 1.92105e-48 after operation: * Diameter was 1.92279e-48 after operation: /= Diameter was 3.65156e-47 after operation: -= Diameter was -0 after operation: Default construct Diameter was 3.65156e-47 after operation: * Diameter was 3.65163e-47 after operation: /= Diameter was 6.93803e-46 after operation: -= Diameter was -0 after operation: Default construct Diameter was 6.93803e-46 after operation: * Diameter was 6.93803e-46 after operation: /= Diameter was 1.31823e-44 after operation: -= Diameter was -0 after operation: Default construct Diameter was 1.31823e-44 after operation: * Diameter was 1.31823e-44 after operation: /= Diameter was 2.50463e-43 after operation: -= Diameter was -0 after operation: Default construct Diameter was 2.50463e-43 after operation: * Diameter was 2.50463e-43 after operation: /= Diameter was 4.7588e-42 after operation: -= Diameter was -0 after operation: Default construct Diameter was 4.7588e-42 after operation: * Diameter was 4.7588e-42 after operation: /= Diameter was 9.04171e-41 after operation: -= Diameter was -0 after operation: Default construct Diameter was 9.04171e-41 after operation: * Diameter was 9.04171e-41 after operation: /= Diameter was 1.71793e-39 after operation: -= Diameter was -0 after operation: Default construct Diameter was 1.71793e-39 after operation: * Diameter was 1.71793e-39 after operation: /= Diameter was 3.26406e-38 after operation: -= Diameter was -0 after operation: Default construct Diameter was 3.26406e-38 after operation: * Diameter was 3.26406e-38 after operation: /= Diameter was 6.20171e-37 after operation: -= Diameter was -0 after operation: Default construct Diameter was 6.20171e-37 after operation: * Diameter was 6.20171e-37 after operation: /= Diameter was 1.17832e-35 after operation: -= Diameter was -0 after operation: Default construct Diameter was 1.17832e-35 after operation: * Diameter was 1.17832e-35 after operation: /= Diameter was 2.23882e-34 after operation: -= Final value was: {1e-14,1e-14}