Mixed precision arithmetic is fully supported by the library.
There are two different forms:
If the arguments to a binary operator are of different precision, then the operation is allowed as long as there is an unambiguous implicit conversion from one argument type to the other. In all cases the arithmetic is performed "as if" the lower precision type is promoted to the higher precision type before applying the operator. However, particular backends may optimise this and avoid actually creating a temporary if they are able to do so.
mpfr_float_50 a(2), b; mpfr_float_100 c(3), d; static_mpfr_float_50 e(5), f; mpz_int i(20); d = a * c; // OK, result of operand is an mpfr_float_100. b = a * c; // Error, can't convert the result to an mpfr_float_50 as it will lose digits. f = a * e; // Error, operator is ambiguous, result could be of either type. f = e * i; // OK, unambiguous conversion from mpz_int to static_mpfr_float_50
Sometimes you want to apply an operator to two arguments of the same precision in such a way as to obtain a result of higher precision. The most common situation occurs with fixed precision integers, where you want to multiply two N-bit numbers to obtain a 2N-bit result. This is supported in this library by the following free functions:
template <class ResultType, class Source1 class Source2> ResultType& add(ResultType& result, const Source1& a, const Source2& b); template <class ResultType, class Source1 class Source2> ResultType& subtract(ResultType& result, const Source1& a, const Source2& b); template <class ResultType, class Source1 class Source2> ResultType& multiply(ResultType& result, const Source1& a, const Source2& b);
These functions apply the named operator to the arguments a
and b and store the result in result,
returning result. In all cases they behave "as
if" arguments a and b were
first promoted to type
before applying the operator, though particular backends may well avoid that
step by way of an optimization.
be an instance of class
and the types
Source2 may be either instances
number or native
integer types. The latter is an optimization that allows arithmetic to be
performed on native integer types producing an extended precision result.
#include <boost/multiprecision/cpp_int.hpp> using namespace boost::multiprecision; boost::uint64_t i = (std::numeric_limits<boost::uint64_t>::max)(); boost::uint64_t j = 1; uint128_t ui128; uint256_t ui256; // // Start by performing arithmetic on 64-bit integers to yield 128-bit results: // std::cout << std::hex << std::showbase << i << std::endl; std::cout << std::hex << std::showbase << add(ui128, i, j) << std::endl; std::cout << std::hex << std::showbase << multiply(ui128, i, i) << std::endl; // // The try squaring a 128-bit integer to yield a 256-bit result: // ui128 = (std::numeric_limits<uint128_t>::max)(); std::cout << std::hex << std::showbase << multiply(ui256, ui128, ui128) << std::endl;
Produces the output:
0xffffffffffffffff 0x10000000000000000 0xFFFFFFFFFFFFFFFE0000000000000001 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE00000000000000000000000000000001
The following backends have at least some direct support for mixed precision arithmetic, and therefore avoid creating unnecessary temporaries when using the interfaces above. Therefore when using these types it's more efficient to use mixed precision arithmetic, than it is to explicitly cast the operands to the result type: