diff --git a/include/boost/multiprecision/detail/no_et_ops.hpp b/include/boost/multiprecision/detail/no_et_ops.hpp index 2ab16b67..c564979d 100644 --- a/include/boost/multiprecision/detail/no_et_ops.hpp +++ b/include/boost/multiprecision/detail/no_et_ops.hpp @@ -52,7 +52,7 @@ namespace boost { BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if >::value, number >::type operator+(const number& a, const V& b) { - detail::scoped_default_precision > precision_guard(a); + detail::scoped_default_precision > precision_guard(a, b); number result; using default_ops::eval_add; eval_add(result.backend(), a.backend(), number::canonical_value(b)); @@ -62,7 +62,7 @@ namespace boost { BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if >::value && !is_equivalent_number_type::value, number >::type operator+(const V& a, const number& b) { - detail::scoped_default_precision > precision_guard(b); + detail::scoped_default_precision > precision_guard(b, a); number result; using default_ops::eval_add; eval_add(result.backend(), b.backend(), number::canonical_value(a)); @@ -84,7 +84,7 @@ namespace boost { BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if >::value, number >::type operator-(const number& a, const V& b) { - detail::scoped_default_precision > precision_guard(a); + detail::scoped_default_precision > precision_guard(a, b); number result; using default_ops::eval_subtract; eval_subtract(result.backend(), a.backend(), number::canonical_value(b)); @@ -94,7 +94,7 @@ namespace boost { BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if >::value && !is_equivalent_number_type::value, number >::type operator-(const V& a, const number& b) { - detail::scoped_default_precision > precision_guard(b); + detail::scoped_default_precision > precision_guard(b, a); number result; using default_ops::eval_subtract; eval_subtract(result.backend(), number::canonical_value(a), b.backend()); @@ -116,7 +116,7 @@ namespace boost { BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if >::value, number >::type operator*(const number& a, const V& b) { - detail::scoped_default_precision > precision_guard(a); + detail::scoped_default_precision > precision_guard(a, b); number result; using default_ops::eval_multiply; eval_multiply(result.backend(), a.backend(), number::canonical_value(b)); @@ -126,7 +126,7 @@ namespace boost { BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if >::value && !is_equivalent_number_type::value, number >::type operator*(const V& a, const number& b) { - detail::scoped_default_precision > precision_guard(b); + detail::scoped_default_precision > precision_guard(b, a); number result; using default_ops::eval_multiply; eval_multiply(result.backend(), b.backend(), number::canonical_value(a)); @@ -148,7 +148,7 @@ namespace boost { BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if >::value, number >::type operator/(const number& a, const V& b) { - detail::scoped_default_precision > precision_guard(a); + detail::scoped_default_precision > precision_guard(a, b); number result; using default_ops::eval_divide; eval_divide(result.backend(), a.backend(), number::canonical_value(b)); @@ -158,7 +158,7 @@ namespace boost { BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if >::value && !is_equivalent_number_type::value, number >::type operator/(const V& a, const number& b) { - detail::scoped_default_precision > precision_guard(b); + detail::scoped_default_precision > precision_guard(b, a); number result; using default_ops::eval_divide; eval_divide(result.backend(), number::canonical_value(a), b.backend()); diff --git a/include/boost/multiprecision/detail/precision.hpp b/include/boost/multiprecision/detail/precision.hpp index 641c78a4..a185f553 100644 --- a/include/boost/multiprecision/detail/precision.hpp +++ b/include/boost/multiprecision/detail/precision.hpp @@ -197,6 +197,20 @@ inline BOOST_MP_CXX14_CONSTEXPR void maybe_promote_precision(T* obj) #define BOOST_MP_CONSTEXPR_IF_VARIABLE_PRECISION(T) if (boost::multiprecision::detail::is_variable_precision::value) #endif +template +struct scoped_target_precision +{ + variable_precision_options opts; + scoped_target_precision() : opts(T::thread_default_variable_precision_options()) + { + T::thread_default_variable_precision_options(variable_precision_options::preserve_target_precision, variable_precision_options::precision_group); + } + ~scoped_target_precision() + { + T::thread_default_variable_precision_options(opts); + } +}; + } } } // namespace boost::multiprecision::detail diff --git a/include/boost/multiprecision/mpc.hpp b/include/boost/multiprecision/mpc.hpp index e100ac25..8bc1a110 100644 --- a/include/boost/multiprecision/mpc.hpp +++ b/include/boost/multiprecision/mpc.hpp @@ -562,7 +562,7 @@ struct mpc_complex_backend<0> : public detail::mpc_complex_imp<0> } template mpc_complex_backend(const mpfr_float_backend& val) - : detail::mpc_complex_imp<0>(mpfr_get_prec(val.data())) + : detail::mpc_complex_imp<0>(this->preserve_source_precision() ? mpfr_get_prec(val.data()) : multiprecision::detail::digits10_2_2(this->get_default_precision())) { mpc_set_fr(this->m_data, val.data(), GMP_RNDN); } @@ -707,7 +707,8 @@ struct mpc_complex_backend<0> : public detail::mpc_complex_imp<0> template mpc_complex_backend& operator=(const mpfr_float_backend& val) { - mpc_set_prec(this->m_data, mpfr_get_prec(val.data())); + if(this->preserve_source_precision()) + mpc_set_prec(this->m_data, mpfr_get_prec(val.data())); mpc_set_fr(this->m_data, val.data(), GMP_RNDN); return *this; } @@ -1160,10 +1161,13 @@ inline void assign_components(mpc_complex_backend& result, const mpfr_float_ // This is called from class number's constructors, so if we have variable // precision, then copy the precision of the source variables. // - if (!D1) + BOOST_IF_CONSTEXPR(!D1) { - unsigned long prec = (std::max)(mpfr_get_prec(a.data()), mpfr_get_prec(b.data())); - mpc_set_prec(result.data(), prec); + if ((result.thread_default_variable_precision_options() & variable_precision_options::precision_group) == variable_precision_options::preserve_source_precision) + { + unsigned long prec = (std::max)(mpfr_get_prec(a.data()), mpfr_get_prec(b.data())); + mpc_set_prec(result.data(), prec); + } } using default_ops::eval_fpclassify; if (eval_fpclassify(a) == (int)FP_NAN) diff --git a/include/boost/multiprecision/mpfi.hpp b/include/boost/multiprecision/mpfi.hpp index efa11d21..aef4043f 100644 --- a/include/boost/multiprecision/mpfi.hpp +++ b/include/boost/multiprecision/mpfi.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -93,6 +94,13 @@ struct mpfi_float_imp if (o.m_data[0].left._mpfr_d) mpfi_set(m_data, o.m_data); } + template + mpfi_float_imp(const mpfr_float_imp& o) + { + mpfi_init2(m_data, preserve_source_precision() ? mpfr_get_prec(o.data()) : boost::multiprecision::detail::digits10_2_2(get_default_precision())); + if (o.data()[0]._mpfr_d) + mpfi_set_fr(m_data, o.data()); + } // rvalue copy mpfi_float_imp(mpfi_float_imp&& o) noexcept { @@ -386,6 +394,10 @@ struct mpfi_float_backend : public detail::mpfi_float_imp { mpfi_set(this->m_data, val.data()); } + template + mpfi_float_backend(const mpfr_float_backend& val, typename std::enable_if::type* = 0) + : detail::mpfi_float_imp(val) {} + template explicit mpfi_float_backend(const mpfi_float_backend& val, typename std::enable_if::type* = 0) : detail::mpfi_float_imp() @@ -463,6 +475,11 @@ struct mpfi_float_backend<0> : public detail::mpfi_float_imp<0> mpfi_set(this->m_data, val); } mpfi_float_backend(const mpfi_float_backend& o) : detail::mpfi_float_imp<0>(o) {} + + template + mpfi_float_backend(const mpfr_float_backend& val) + : detail::mpfi_float_imp<0>(val) {} + // rvalue copy mpfi_float_backend(mpfi_float_backend&& o) noexcept : detail::mpfi_float_imp<0>(static_cast&&>(o)) {} @@ -475,6 +492,7 @@ struct mpfi_float_backend<0> : public detail::mpfi_float_imp<0> mpfi_float_backend(const V& a, const V& b, unsigned digits10) : detail::mpfi_float_imp<0>(multiprecision::detail::digits10_2_2(digits10)) { + boost::multiprecision::detail::scoped_target_precision > opts; assign_components(*this, a, b); } @@ -850,6 +868,19 @@ inline void eval_convert_to(long double* result, const mpfi_float_backend inline void assign_components(mpfi_float_backend& result, const mpfr_float_backend& a, const mpfr_float_backend& b) { + // + // This is called from class number's constructors, so if we have variable + // precision, then copy the precision of the source variables. + // + BOOST_IF_CONSTEXPR(!D1) + { + if ((result.thread_default_variable_precision_options() & variable_precision_options::precision_group) == variable_precision_options::preserve_source_precision) + { + unsigned long prec = (std::max)(mpfr_get_prec(a.data()), mpfr_get_prec(b.data())); + mpfi_set_prec(result.data(), prec); + } + } + using default_ops::eval_fpclassify; if (eval_fpclassify(a) == (int)FP_NAN) { diff --git a/include/boost/multiprecision/number.hpp b/include/boost/multiprecision/number.hpp index f3e9ae1b..e8315c79 100644 --- a/include/boost/multiprecision/number.hpp +++ b/include/boost/multiprecision/number.hpp @@ -409,6 +409,23 @@ class number typename std::enable_if::value, number&>::type BOOST_MP_CXX14_CONSTEXPR operator+=(const V& v) { + detail::scoped_default_precision > precision_guard(*this, v); + // + // If the current precision of *this differs from that of value v, then we + // create a temporary (which will have the correct precision thanks to precision_guard) + // and then move the result into *this. In C++17 we add a leading "if constexpr" + // which causes this code to be eliminated in the common case that this type is + // not actually variable precision. Pre C++17 this code should still be mostly + // optimised away, but we can't prevent instantiation of the dead code leading + // to longer build and possibly link times. + // + BOOST_MP_CONSTEXPR_IF_VARIABLE_PRECISION(number) + if (precision_guard.precision() != boost::multiprecision::detail::current_precision_of(*this)) + { + number t(*this + v); + return *this = std::move(t); + } + using default_ops::eval_add; eval_add(m_backend, canonical_value(v)); return *this; @@ -457,6 +474,23 @@ class number BOOST_MP_CXX14_CONSTEXPR typename std::enable_if::value, number&>::type operator-=(const V& v) { + detail::scoped_default_precision > precision_guard(*this, v); + // + // If the current precision of *this differs from that of value v, then we + // create a temporary (which will have the correct precision thanks to precision_guard) + // and then move the result into *this. In C++17 we add a leading "if constexpr" + // which causes this code to be eliminated in the common case that this type is + // not actually variable precision. Pre C++17 this code should still be mostly + // optimised away, but we can't prevent instantiation of the dead code leading + // to longer build and possibly link times. + // + BOOST_MP_CONSTEXPR_IF_VARIABLE_PRECISION(number) + if (precision_guard.precision() != boost::multiprecision::detail::current_precision_of(*this)) + { + number t(*this - v); + return *this = std::move(t); + } + using default_ops::eval_subtract; eval_subtract(m_backend, canonical_value(v)); return *this; @@ -536,6 +570,23 @@ class number BOOST_MP_CXX14_CONSTEXPR typename std::enable_if::value, number&>::type operator*=(const V& v) { + detail::scoped_default_precision > precision_guard(*this, v); + // + // If the current precision of *this differs from that of value v, then we + // create a temporary (which will have the correct precision thanks to precision_guard) + // and then move the result into *this. In C++17 we add a leading "if constexpr" + // which causes this code to be eliminated in the common case that this type is + // not actually variable precision. Pre C++17 this code should still be mostly + // optimised away, but we can't prevent instantiation of the dead code leading + // to longer build and possibly link times. + // + BOOST_MP_CONSTEXPR_IF_VARIABLE_PRECISION(number) + if (precision_guard.precision() != boost::multiprecision::detail::current_precision_of(*this)) + { + number t(*this + v); + return *this = std::move(t); + } + using default_ops::eval_multiply; eval_multiply(m_backend, canonical_value(v)); return *this; @@ -689,6 +740,23 @@ class number BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if::value, number&>::type operator/=(const V& v) { + detail::scoped_default_precision > precision_guard(*this, v); + // + // If the current precision of *this differs from that of value v, then we + // create a temporary (which will have the correct precision thanks to precision_guard) + // and then move the result into *this. In C++17 we add a leading "if constexpr" + // which causes this code to be eliminated in the common case that this type is + // not actually variable precision. Pre C++17 this code should still be mostly + // optimised away, but we can't prevent instantiation of the dead code leading + // to longer build and possibly link times. + // + BOOST_MP_CONSTEXPR_IF_VARIABLE_PRECISION(number) + if (precision_guard.precision() != boost::multiprecision::detail::current_precision_of(*this)) + { + number t(*this + v); + return *this = std::move(t); + } + using default_ops::eval_divide; eval_divide(m_backend, canonical_value(v)); return *this; diff --git a/test/test_preserve_source_precision.cpp b/test/test_preserve_source_precision.cpp index 01eb6ff7..1fde2689 100644 --- a/test/test_preserve_source_precision.cpp +++ b/test/test_preserve_source_precision.cpp @@ -101,6 +101,78 @@ void test() BOOST_CHECK_EQUAL(a.precision(), 100); a = new_value(); BOOST_CHECK_EQUAL(a.precision(), 35); + + if constexpr (!std::is_same_v) + { + // + // If we have a component type: ie we are an interval or a complex number, then + // operations involving the component type should match those of T: + // + using component_t = typename T::value_type; + component_t::thread_default_precision(100); + component_t::thread_default_variable_precision_options(boost::multiprecision::variable_precision_options::preserve_source_precision); + + component_t cp1("0.1"), cp2("0.3"), cp3("0.11"), cp4("0.1231"); + + BOOST_CHECK_EQUAL(cp1.precision(), 100); + BOOST_CHECK_EQUAL(cp2.precision(), 100); + + component_t::thread_default_precision(35); + + T aa(cp1); + BOOST_CHECK_EQUAL(aa.precision(), 100); + T cc(cp1, cp2); + BOOST_CHECK_EQUAL(cc.precision(), 100); + T dd(cp1, cp2, 55); + BOOST_CHECK_EQUAL(dd.precision(), 55); + aa = new_value(); + BOOST_CHECK_EQUAL(aa.precision(), 35); + if constexpr (std::is_assignable_v) + { + aa = cp1; + BOOST_CHECK_EQUAL(aa.precision(), 100); + aa = new_value(); + BOOST_CHECK_EQUAL(aa.precision(), 35); + aa = std::move(cp1); + BOOST_CHECK_EQUAL(aa.precision(), 100); + aa = new_value(); + BOOST_CHECK_EQUAL(aa.precision(), 35); + } + T bb(std::move(cp2)); + BOOST_CHECK_EQUAL(bb.precision(), 100); + bb = new_value(); + BOOST_CHECK_EQUAL(bb.precision(), 35); + + if constexpr (boost::multiprecision::is_compatible_arithmetic_type::value) + { + aa = bb + cp3; + BOOST_CHECK_EQUAL(aa.precision(), 100); + aa = new_value(); + BOOST_CHECK_EQUAL(aa.precision(), 35); + aa = cp3 * bb; + BOOST_CHECK_EQUAL(aa.precision(), 100); + aa = new_value(); + BOOST_CHECK_EQUAL(aa.precision(), 35); + aa += cp3; + BOOST_CHECK_EQUAL(aa.precision(), 100); + aa = new_value(); + aa -= cp3; + BOOST_CHECK_EQUAL(aa.precision(), 100); + aa = new_value(); + BOOST_CHECK_EQUAL(aa.precision(), 35); + aa *= cp4; + BOOST_CHECK_EQUAL(aa.precision(), 100); + aa = new_value(); + aa /= cp4; + BOOST_CHECK_EQUAL(aa.precision(), 100); + aa = new_value(); + BOOST_CHECK_EQUAL(aa.precision(), 35); + aa -= bb * cp3; + BOOST_CHECK_EQUAL(aa.precision(), 100); + aa = new_value(); + BOOST_CHECK_EQUAL(aa.precision(), 35); + } + } } int main() diff --git a/test/test_preserve_target_precision.cpp b/test/test_preserve_target_precision.cpp index 721e99c2..d9f7409a 100644 --- a/test/test_preserve_target_precision.cpp +++ b/test/test_preserve_target_precision.cpp @@ -77,6 +77,52 @@ void test() BOOST_CHECK_EQUAL(a.precision(), 35); a -= b * hp3; BOOST_CHECK_EQUAL(a.precision(), 35); + + if constexpr (!std::is_same_v) + { + // + // If we have a component type: ie we are an interval or a complex number, then + // operations involving the component type should match those of T: + // + using component_t = typename T::value_type; + component_t::thread_default_precision(100); + component_t::thread_default_variable_precision_options(boost::multiprecision::variable_precision_options::preserve_source_precision); + + component_t cp1("0.1"), cp2("0.3"), cp3("0.11"), cp4("0.1231"); + + BOOST_CHECK_EQUAL(cp1.precision(), 100); + BOOST_CHECK_EQUAL(cp2.precision(), 100); + + T::thread_default_precision(35); + + T aa(cp1); + BOOST_CHECK_EQUAL(aa.precision(), 35); + T cc(cp1, cp2); + BOOST_CHECK_EQUAL(cc.precision(), 35); + T dd(cp1, cp2, 20); + BOOST_CHECK_EQUAL(dd.precision(), 20); + aa = cp1; + BOOST_CHECK_EQUAL(aa.precision(), 35); + aa = std::move(cp1); + BOOST_CHECK_EQUAL(aa.precision(), 35); + T bb(std::move(cp2)); + BOOST_CHECK_EQUAL(bb.precision(), 35); + + aa = bb + cp3; + BOOST_CHECK_EQUAL(aa.precision(), 35); + aa = cp3 * bb; + BOOST_CHECK_EQUAL(aa.precision(), 35); + aa += cp3; + BOOST_CHECK_EQUAL(aa.precision(), 35); + aa -= cp3; + BOOST_CHECK_EQUAL(aa.precision(), 35); + aa *= cp4; + BOOST_CHECK_EQUAL(aa.precision(), 35); + aa /= cp4; + BOOST_CHECK_EQUAL(aa.precision(), 35); + aa -= bb * cp3; + BOOST_CHECK_EQUAL(aa.precision(), 35); + } } int main()