// Copyright 2023 - 2024 Matt Borland // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt #ifndef BOOST_DECIMAL_DETAIL_MUL_IMPL_HPP #define BOOST_DECIMAL_DETAIL_MUL_IMPL_HPP #include #include #include #include "int128.hpp" #include #include #include #include "int128.hpp" #ifndef BOOST_DECIMAL_BUILD_MODULE #include #endif namespace boost { namespace decimal { namespace detail { // Each type has two different multiplication impls // 1) Returns a decimal type and lets the constructor handle with shrinking the significand // 2) Returns a struct of the constituent components (used with FMAs) template BOOST_DECIMAL_FORCE_INLINE constexpr auto mul_impl(const T& lhs, const T& rhs) noexcept -> ReturnType { using mul_type = std::conditional_t < 64, std::uint_fast64_t, int128::uint128_t>; // The constructor needs to calculate the number of digits in the significand which for uint128 is slow // Since we know the value of res_sig is constrained to [1'000'000^2, 9'999'999^2] which equates to // either 13 or 14 decimal digits we can use a single division to make binary search occur with // uint32_t instead. 14 - 5 = 9 or 13 - 5 = 8 which are both still greater than or equal to // digits10 + 1 for rounding which is 8 decimal digits auto res_sig {(static_cast(lhs.full_significand()) * static_cast(rhs.full_significand()))}; auto res_exp {lhs.biased_exponent() + rhs.biased_exponent()}; return {res_sig, res_exp, lhs.isneg() != rhs.isneg()}; } template BOOST_DECIMAL_FORCE_INLINE constexpr auto mul_impl(T lhs_sig, U lhs_exp, bool lhs_sign, T rhs_sig, U rhs_exp, bool rhs_sign) noexcept -> ReturnType { using mul_type = std::uint_fast64_t; // The constructor needs to calculate the number of digits in the significand which for uint128 is slow // Since we know the value of res_sig is constrained to [1'000'000^2, 9'999'999^2] which equates to // either 13 or 14 decimal digits we can use a single division to make binary search occur with // uint32_t instead. 14 - 5 = 9 or 13 - 5 = 8 which are both still greater than or equal to // digits10 + 1 for rounding which is 8 decimal digits constexpr auto ten_pow_five {pow10(static_cast(5))}; auto res_sig {(static_cast(lhs_sig) * static_cast(rhs_sig)) / ten_pow_five}; auto res_exp {lhs_exp + rhs_exp + static_cast(5)}; return {static_cast(res_sig), res_exp, lhs_sign != rhs_sign}; } // In the fast case we are better served doing our 128-bit division here since we are at a know starting point template constexpr auto d64_mul_impl(const T& lhs, const T& rhs) noexcept -> ReturnType { using unsigned_int128_type = boost::int128::uint128_t; // Once we have the normalized significands and exponents all we have to do is // multiply the significands and add the exponents // // The constructor needs to calculate the number of digits in the significand which for uint128 is slow // Since we know the value of res_sig is constrained to [(10^16)^2, (10^17 - 1)^2] which equates to // either 31 or 32 decimal digits we can use a single division to make binary search occur with // uint_fast64_t instead. 32 - 13 = 19 or 31 - 13 = 18 which are both still greater than // digits10 + 1 for rounding which is 17 decimal digits constexpr auto ten_pow_13 {pow10(static_cast(13))}; const auto res_sig {static_cast((static_cast(lhs.full_significand()) * static_cast(rhs.full_significand())) / ten_pow_13)}; const auto res_exp {lhs.biased_exponent() + rhs.biased_exponent() + 13}; return ReturnType{res_sig, res_exp, lhs.isneg() != rhs.isneg()}; } template BOOST_DECIMAL_FORCE_INLINE constexpr auto d64_mul_impl(T lhs_sig, U lhs_exp, bool lhs_sign, T rhs_sig, U rhs_exp, bool rhs_sign) noexcept -> std::enable_if_t, ReturnType> { using unsigned_int128_type = boost::int128::uint128_t; // Once we have the normalized significands and exponents all we have to do is // multiply the significands and add the exponents // // The constructor needs to calculate the number of digits in the significand which for uint128 is slow // Since we know the value of res_sig is constrained to [(10^16)^2, (10^17 - 1)^2] which equates to // either 31 or 32 decimal digits we can use a single division to make binary search occur with // uint_fast64_t instead. 32 - 13 = 19 or 31 - 13 = 18 which are both still greater than // digits10 + 1 for rounding which is 17 decimal digits constexpr auto ten_pow_13 {pow10(static_cast(13))}; auto res_sig {(static_cast(lhs_sig) * static_cast(rhs_sig)) / ten_pow_13}; auto res_exp {lhs_exp + rhs_exp + static_cast(13)}; return {static_cast(res_sig), res_exp, lhs_sign != rhs_sign}; } template BOOST_DECIMAL_FORCE_INLINE constexpr auto d64_mul_impl(T lhs_sig, U lhs_exp, bool lhs_sign, T rhs_sig, U rhs_exp, bool rhs_sign) noexcept -> std::enable_if_t, ReturnType> { using unsigned_int128_type = boost::int128::uint128_t; constexpr auto comp_value {detail::pow10(static_cast(31))}; #ifdef BOOST_DECIMAL_DEBUG std::cerr << "sig lhs: " << sig_lhs << "\nexp lhs: " << exp_lhs << "\nsig rhs: " << sig_rhs << "\nexp rhs: " << exp_rhs; #endif bool sign {lhs_sign != rhs_sign}; // Once we have the normalized significands and exponents all we have to do is // multiply the significands and add the exponents auto res_sig {static_cast(lhs_sig) * static_cast(rhs_sig)}; auto res_exp {static_cast(lhs_exp + rhs_exp)}; const auto sig_dig {res_sig >= comp_value ? 32 : 31}; constexpr auto max_dig {std::numeric_limits::digits10}; res_sig /= detail::pow10(static_cast(sig_dig - max_dig)); res_exp += sig_dig - max_dig; const auto res_sig_64 {static_cast(res_sig)}; #ifdef BOOST_DECIMAL_DEBUG std::cerr << "\nres sig: " << res_sig_64 << "\nres exp: " << res_exp << std::endl; #endif return {res_sig_64, res_exp, sign}; } template BOOST_DECIMAL_FORCE_INLINE constexpr auto d128_mul_impl(const T1& lhs_sig, const U1 lhs_exp, const bool lhs_sign, const T2& rhs_sig, const U2 rhs_exp, const bool rhs_sign) noexcept -> ReturnType { using sig_type = T1; static_assert(std::is_same::value, "Should have a common type by this point"); const bool sign {lhs_sign != rhs_sign}; auto res_sig {detail::umul256(lhs_sig, rhs_sig)}; auto res_exp {lhs_exp + rhs_exp}; const auto sig_dig {detail::num_digits(res_sig)}; // 34 is the number of digits in the d128 significand // this way we can skip rounding in the constructor a second time const auto digit_delta {sig_dig - std::numeric_limits::digits10}; if (BOOST_DECIMAL_LIKELY(digit_delta > 0)) { auto biased_exp {res_exp + detail::bias_v}; detail::coefficient_rounding(res_sig, res_exp, biased_exp, sign, sig_dig); } BOOST_DECIMAL_ASSERT((res_sig[3] | res_sig[2]) == 0U); return {int128::uint128_t{res_sig[1], res_sig[0]}, res_exp, sign}; } template BOOST_DECIMAL_FORCE_INLINE constexpr auto d128_fast_mul_impl(const T1& lhs_sig, const U1 lhs_exp, const bool lhs_sign, const T2& rhs_sig, const U2 rhs_exp, const bool rhs_sign) noexcept -> ReturnType { const bool sign {lhs_sign != rhs_sign}; // Once we have the normalized significands and exponents all we have to do is // multiply the significands and add the exponents auto res_sig {detail::umul256(lhs_sig, rhs_sig)}; const auto res_exp {lhs_exp + rhs_exp + 30}; constexpr auto ten_pow_30 {detail::pow10(static_cast(30))}; res_sig /= ten_pow_30; BOOST_DECIMAL_ASSERT((res_sig[3] | res_sig[2]) == 0U); // LCOV_EXCL_LINE return {int128::uint128_t{res_sig[1], res_sig[0]}, res_exp, sign}; } template BOOST_DECIMAL_FORCE_INLINE auto mul_impl(const decimal128_t_components& lhs, const decimal128_t_components& rhs) noexcept -> ReturnType { return d128_mul_impl(lhs.sig, lhs.exp, lhs.sign, rhs.sig, rhs.exp, rhs.sign); } } // namespace detail } // namespace decimal } // namespace boost #endif //BOOST_DECIMAL_DETAIL_MUL_IMPL_HPP