| Home | Libraries | People | FAQ | More

##### Drop-in Caveats

###### Using Boost.Multiprecision `cpp_float` types for numerical calculations with higher precision than fundamental (built-in) `long double`.

The Boost.Multiprecision library can be used for computations requiring precision exceeding that of standard fundamental (built-in) types such as `float`, `double` and `long double`. For extended-precision calculations, Boost.Multiprecision supplies several template data types called `cpp_bin_float_`.

The number of decimal digits of precision is fixed at compile-time via template parameter.

To use these floating-point types and Boost.Math collection of high-precision constants, we need some includes:

    #include <boost/math/constants/constants.hpp>

    #include <boost/multiprecision/cpp_bin_float.hpp>
    // that includes some predefined typedefs like:
    // using boost::multiprecision::cpp_bin_float_quad;
    // using boost::multiprecision::cpp_bin_float_50;
    // using boost::multiprecision::cpp_bin_float_100;

    #include <iostream>
    #include <limits>

So now we can demonstrate with some trivial calculations:

Using a `typedef` like `cpp_bin_float_50` hides the complexity of multiprecision, and allows us to define variables with 50 decimal digit precision just like fundamental (built-in) `double`.

    using boost::multiprecision::cpp_bin_float_50;

    cpp_bin_float_50 seventh = cpp_bin_float_50(1) / 7;  // 1 / 7

By default, output would only show the standard 6 decimal digits, so set precision to show all 50 significant digits, including any trailing zeros (to show the full implied 50 digit precision).

    std::cout.precision(std::numeric_limits<cpp_bin_float_50>::digits10); // Show 50 decimal digit precision.
    std::cout << std::showpoint << std::endl; // Append any trailing zeros.
    std::cout << seventh << std::endl;

which outputs:

    0.14285714285714285714285714285714285714285714285714

We can also use Boost.Math Boost.Math constants like π, guaranteed to be initialized with the very last bit of precision for the floating-point type.

    std::cout << "pi = " << boost::math::constants::pi<cpp_bin_float_50>() << std::endl;
    cpp_bin_float_50 circumference = boost::math::constants::pi<cpp_bin_float_50>() * 2 * seventh;
    std::cout << "c =  "<< circumference << std::endl;

which outputs

    pi = 3.1415926535897932384626433832795028841971693993751

    c =  0.89759790102565521098932668093700082405633411410717

So using `cpp_bin_float_50` looks like a simple 'drop-in' for the fundamental (built-in) type like 'double', but beware of less-than-expected precision from construction or conversion from `double` or other lower precision types. This is a mistake that is very easy to make, and very difficult to detect because the difference in precision is only visible after about the 17th decimal digit.

We can show this by constructing a fraction one seventh from `double`:

    cpp_bin_float_50 seventh_0 = cpp_bin_float_50(1/7);  // Avoid the schoolboy-error `double d7 = 1 / 7;` giving zero!
    std::cout << "seventh_0 = "  << seventh_0 << std::endl;
    // seventh_double0 = 0.0000000000000000000000000000000000000000000000000

    cpp_bin_float_50 seventh_double = cpp_bin_float_50(1./7);  // Construct from double! (0.14285714285714)
    std::cout << "seventh_double = "  << seventh_double << std::endl; // Boost.Multiprecision post-school error!
    // seventh_double = 0.14285714285714284921269268124888185411691665649414

Did you spot the probably-unintended difference? After the 17th decimal digit, result is apparently random and not the expected recurring pattern 14285714285714...

The 'random' digits after digit 17 are from the `cpp_bin_float_50` representation of the `double` value 0.14285714285714 which is different from the 'better' `cpp_bin_float_50` representation of the fraction 1/7

    cpp_bin_float_50 seventh_big(1);  // 1
    seventh_big /= 7;
    std::cout << "seventh_big = " << seventh_big << std::endl; //
    // seventh_big     = 0.14285714285714285714285714285714285714285714285714

Note the recurring 14285714285714 pattern as expected. We can also construct a `const` version (but not yet `constexpr`) and evaluate in a single expression:

    const cpp_bin_float_50 seventh_const (cpp_bin_float_50(1) / 7);
    std::cout << "seventh_const = " << seventh_const << std::endl; //
    // seventh_const = 0.14285714285714285714285714285714285714285714285714

    // Sadly we cannot (yet) write:
    // constexpr cpp_bin_float_50 any_constexpr(0);

    // constexpr cpp_bin_float_50 seventh_constexpr (cpp_bin_float_50(1) / 7);
    // std::cout << "seventh_constexpr = " << seventh_constexpr << std::endl; //
    // nor use the macro constexpr unless it returns `const`
    // constexpr cpp_bin_float_50 seventh_constexpr(seventh_const);

For some purposes, this difference in precision may be insignificant, but if one is implementing a formula involving a fraction from integers, including decimal fractions like 1/10, 1/100, then comparison with other computations like Wolfram Alpha will reveal differences whose cause may be perplexing.

To get as precise-as-possible decimal fractions like 1.234, we can write

    const cpp_bin_float_50 f1(cpp_bin_float_50(1234) / 1000);  // Construct from a fraction.
    std::cout << "cpp_bin_float_50 f1(cpp_bin_float_50(1234) / 1000) = " << f1 << std::endl; // cpp_bin_float_50 f1(cpp_bin_float_50(1234) / 1000) = 1.2340000000000000000000000000000000000000000000000

or

    const cpp_bin_float_50 f2("1.234");  // Construct from decimal digit string.
    std::cout << "cpp_bin_float_50 f2(\"1.234\") = " << f2 << std::endl; // cpp_bin_float_50 f2("1.234") = 1.2340000000000000000000000000000000000000000000000

that are different from constructing from a `double` with value 1.234

    const cpp_bin_float_50 f3(cpp_bin_float_50(1.234));
    std::cout << "cpp_bin_float_50 f3(cpp_bin_float_50(1.234)) = " << f3 << std::endl; // 1.2339999999999999857891452847979962825775146484375

Typical output is:

    0.14285714285714285714285714285714285714285714285714
    pi = 3.1415926535897932384626433832795028841971693993751
    c =  0.89759790102565521098932668093700082405633411410717
    seventh_0 = 0.0000000000000000000000000000000000000000000000000
    seventh_double = 0.14285714285714284921269268124888185411691665649414
    seventh_big = 0.14285714285714285714285714285714285714285714285714
    seventh_const = 0.14285714285714285714285714285714285714285714285714
    cpp_bin_float_50 f1(cpp_bin_float_50(1234) / 100) = 12.340000000000000000000000000000000000000000000000
    cpp_bin_float_50 f2("1.234") = 1.2340000000000000000000000000000000000000000000000
    cpp_bin_float_50 f3(cpp_bin_float_50(1.234)) = 1.2339999999999999857891452847979962825775146484375