Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add one-shot decoding for 64-bit types and improve mul #866

Merged
merged 8 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 49 additions & 19 deletions include/boost/decimal/decimal64.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <boost/decimal/detail/mul_impl.hpp>
#include <boost/decimal/detail/div_impl.hpp>
#include <boost/decimal/detail/promote_significand.hpp>
#include <boost/decimal/detail/components.hpp>

#ifndef BOOST_DECIMAL_BUILD_MODULE

Expand Down Expand Up @@ -121,16 +122,6 @@ BOOST_DECIMAL_CONSTEXPR_VARIABLE std::uint64_t d64_construct_combination_mask =
BOOST_DECIMAL_CONSTEXPR_VARIABLE std::uint64_t d64_construct_exp_mask = 0b0'00000'11111111'0000000000'0000000000'0000000000'0000000000'0000000000;
BOOST_DECIMAL_CONSTEXPR_VARIABLE std::uint64_t d64_construct_significand_mask = d64_no_combination;

struct decimal64_components
{
using significand_type = std::uint64_t;
using biased_exponent_type = std::int32_t;

significand_type sig;
biased_exponent_type exp;
bool sign;
};

} //namespace detail

#if defined(__GNUC__) && __GNUC__ >= 8
Expand Down Expand Up @@ -165,6 +156,8 @@ BOOST_DECIMAL_EXPORT class decimal64 final
constexpr auto isneg() const noexcept -> bool;
constexpr auto edit_sign(bool sign) noexcept -> void;

constexpr auto to_components() const noexcept -> detail::decimal64_components;

// Attempts conversion to integral type:
// If this is nan sets errno to EINVAL and returns 0
// If this is not representable sets errno to ERANGE and returns 0
Expand Down Expand Up @@ -1056,6 +1049,49 @@ constexpr auto decimal64::isneg() const noexcept -> bool
return static_cast<bool>(bits_ & detail::d64_sign_mask);
}

constexpr auto decimal64::to_components() const noexcept -> detail::decimal64_components
{
detail::decimal64_components components {};

exponent_type expval {};
significand_type significand {};

const auto comb_bits {(bits_ & detail::d64_comb_11_mask)};

switch (comb_bits)
{
case detail::d64_comb_11_mask:
expval = (bits_ & detail::d64_comb_11_exp_bits) >> (detail::d64_significand_bits + 1);

// Only need the one bit of T because the other 3 are implied
significand = (bits_ & detail::d64_comb_11_significand_bits) == detail::d64_comb_11_significand_bits ?
UINT64_C(0b1001'0000000000'0000000000'0000000000'0000000000'0000000000) :
UINT64_C(0b1000'0000000000'0000000000'0000000000'0000000000'0000000000);
break;
case detail::d64_comb_10_mask:
expval = UINT64_C(0b1000000000);
significand |= (bits_ & detail::d64_comb_00_01_10_significand_bits) >> detail::d64_exponent_bits;
break;
case detail::d64_comb_01_mask:
expval = UINT64_C(0b0100000000);
significand |= (bits_ & detail::d64_comb_00_01_10_significand_bits) >> detail::d64_exponent_bits;
break;
default:
// Expval = 0
significand |= (bits_ & detail::d64_comb_00_01_10_significand_bits) >> detail::d64_exponent_bits;
break;
}

significand |= (bits_ & detail::d64_significand_mask);
expval |= (bits_ & detail::d64_exponent_mask) >> detail::d64_significand_bits;

components.sig = significand;
components.exp = static_cast<biased_exponent_type>(expval) - detail::bias_v<decimal64>;
components.sign = bits_ & detail::d64_sign_mask;

return components;
}

template <typename T>
constexpr auto decimal64::edit_exponent(T expval) noexcept
BOOST_DECIMAL_REQUIRES_RETURN(detail::is_integral_v, T, void)
Expand Down Expand Up @@ -1393,16 +1429,10 @@ constexpr auto operator*(decimal64 lhs, decimal64 rhs) noexcept -> decimal64
}
#endif

auto lhs_sig {lhs.full_significand()};
auto lhs_exp {lhs.biased_exponent()};
detail::normalize<decimal64>(lhs_sig, lhs_exp);

auto rhs_sig {rhs.full_significand()};
auto rhs_exp {rhs.biased_exponent()};
detail::normalize<decimal64>(rhs_sig, rhs_exp);
const auto lhs_components {lhs.to_components()};
const auto rhs_components {rhs.to_components()};

return detail::d64_mul_impl<decimal64>(lhs_sig, lhs_exp, lhs.isneg(),
rhs_sig, rhs_exp, rhs.isneg());
return detail::d64_mul_impl<decimal64>(lhs_components, rhs_components);
}

template <typename Integer>
Expand Down
6 changes: 4 additions & 2 deletions include/boost/decimal/decimal64_fast.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ BOOST_DECIMAL_EXPORT class decimal64_fast final
friend constexpr auto to_dpd_d64(DecimalType val) noexcept
BOOST_DECIMAL_REQUIRES_RETURN(detail::is_decimal_floating_point_v, DecimalType, std::uint64_t);

template <typename ReturnType, typename T>
BOOST_DECIMAL_FORCE_INLINE friend constexpr auto detail::d64_mul_impl(const T& lhs, const T& rhs) noexcept -> std::enable_if_t<!std::is_same<ReturnType, decimal64>::value, ReturnType>;

public:
constexpr decimal64_fast() noexcept = default;

Expand Down Expand Up @@ -1069,8 +1072,7 @@ constexpr auto operator*(decimal64_fast lhs, decimal64_fast rhs) noexcept -> dec
}
#endif

return detail::d64_mul_impl<decimal64_fast>(lhs.significand_, lhs.biased_exponent(), lhs.sign_,
rhs.significand_, rhs.biased_exponent(), rhs.sign_);
return detail::d64_mul_impl<decimal64_fast>(lhs, rhs);
}

template <typename Integer>
Expand Down
23 changes: 12 additions & 11 deletions include/boost/decimal/detail/components.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ namespace boost {
namespace decimal {
namespace detail {

struct decimal32_components
namespace impl {

template <typename SigType, typename BiasedExpType>
struct decimal_components
{
using significand_type = std::uint32_t;
using biased_exponent_type = std::int32_t;
using significand_type = SigType;
using biased_exponent_type = BiasedExpType;

significand_type sig;
biased_exponent_type exp;
Expand All @@ -40,15 +43,13 @@ struct decimal32_components
}
};

struct decimal32_fast_components
{
using significand_type = std::uint_fast32_t;
using biased_exponent_type = std::int_fast32_t;
} // namespace impl

significand_type sig;
biased_exponent_type exp;
bool sign;
};
using decimal32_components = impl::decimal_components<std::uint32_t, std::int32_t>;

using decimal32_fast_components = impl::decimal_components<std::uint_fast32_t, std::int_fast32_t>;

using decimal64_components = impl::decimal_components<std::uint64_t, std::int32_t>;

} // namespace detail
} // namespace decimal
Expand Down
45 changes: 44 additions & 1 deletion include/boost/decimal/detail/mul_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace detail {
// 2) Returns a struct of the constituent components (used with FMAs)

template <typename ReturnType, typename T>
constexpr auto mul_impl(const T& rhs, const T& lhs) noexcept -> ReturnType
BOOST_DECIMAL_FORCE_INLINE constexpr auto mul_impl(const T& lhs, const T& rhs) noexcept -> ReturnType
{
using mul_type = std::uint_fast64_t;

Expand Down Expand Up @@ -61,6 +61,49 @@ BOOST_DECIMAL_FORCE_INLINE constexpr auto mul_impl(T lhs_sig, U lhs_exp, bool lh
return {static_cast<std::uint32_t>(res_sig), res_exp, lhs_sign != rhs_sign};
}

template <typename ReturnType, typename T>
BOOST_DECIMAL_FORCE_INLINE constexpr auto d64_mul_impl(const T& lhs, const T& rhs) noexcept -> std::enable_if_t<std::is_same<ReturnType, decimal64>::value, ReturnType>
{
// Clang 6-12 yields incorrect results with builtin u128, so we force usage of our version
#if defined(BOOST_DECIMAL_HAS_INT128) && (!defined(__clang_major__) || (__clang_major__) > 12)
using unsigned_int128_type = boost::decimal::detail::uint128_t;
#else
using unsigned_int128_type = boost::decimal::detail::uint128;
#endif

const auto res_sig {(static_cast<unsigned_int128_type>(lhs.full_significand()) * static_cast<unsigned_int128_type>(rhs.full_significand()))};
const auto res_exp {lhs.biased_exponent() + rhs.biased_exponent()};

return {res_sig, res_exp, lhs.isneg() != rhs.isneg()};
}

// In the fast case we are better served doing our 128-bit division here since we are at a know starting point
template <typename ReturnType, typename T>
BOOST_DECIMAL_FORCE_INLINE constexpr auto d64_mul_impl(const T& lhs, const T& rhs) noexcept -> std::enable_if_t<!std::is_same<ReturnType, decimal64>::value, ReturnType>
{
// Clang 6-12 yields incorrect results with builtin u128, so we force usage of our version
#if defined(BOOST_DECIMAL_HAS_INT128) && (!defined(__clang_major__) || (__clang_major__) > 12)
using unsigned_int128_type = boost::decimal::detail::uint128_t;
#else
using unsigned_int128_type = boost::decimal::detail::uint128;
#endif

// 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<unsigned_int128_type>(13))};
const auto res_sig {static_cast<std::uint64_t>((static_cast<unsigned_int128_type>(lhs.full_significand()) * static_cast<unsigned_int128_type>(rhs.full_significand())) / ten_pow_13)};
const auto res_exp {lhs.biased_exponent() + rhs.biased_exponent() + 13};

return {res_sig, res_exp, lhs.isneg() != rhs.isneg()};
}

template <typename ReturnType, BOOST_DECIMAL_INTEGRAL T, BOOST_DECIMAL_INTEGRAL U>
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
Expand Down
Loading