From c4222aa943218c87abde275c918b703cbada7672 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 20 Sep 2024 14:26:53 -0400 Subject: [PATCH 1/3] Improved equality implementations for d32 and d64 --- include/boost/decimal/decimal32.hpp | 13 ++---- include/boost/decimal/decimal64.hpp | 14 ++----- include/boost/decimal/detail/comparison.hpp | 44 +++++++++++++++++++++ 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/include/boost/decimal/decimal32.hpp b/include/boost/decimal/decimal32.hpp index 4c8e0e6fc..a47f04529 100644 --- a/include/boost/decimal/decimal32.hpp +++ b/include/boost/decimal/decimal32.hpp @@ -212,6 +212,9 @@ BOOST_DECIMAL_EXPORT class decimal32 final // NOLINT(cppcoreguidelines-special-m -> std::enable_if_t<(detail::is_decimal_floating_point_v && detail::is_decimal_floating_point_v), bool>; + template + friend constexpr auto equality_impl(DecimalType lhs, DecimalType rhs) noexcept -> bool; + public: // 3.2.2.1 construct/copy/destroy: constexpr decimal32() noexcept = default; @@ -1056,15 +1059,7 @@ constexpr auto decimal32::operator-=(Integer rhs) noexcept constexpr auto operator==(decimal32 lhs, decimal32 rhs) noexcept -> bool { - #ifndef BOOST_DECIMAL_FAST_MATH - if (isnan(lhs) || isnan(rhs)) - { - return false; - } - #endif - - return equal_parts_impl(lhs.full_significand(), lhs.biased_exponent(), lhs.isneg(), - rhs.full_significand(), rhs.biased_exponent(), rhs.isneg()); + return equality_impl(lhs, rhs); } template diff --git a/include/boost/decimal/decimal64.hpp b/include/boost/decimal/decimal64.hpp index ac44a243a..8bf8b3164 100644 --- a/include/boost/decimal/decimal64.hpp +++ b/include/boost/decimal/decimal64.hpp @@ -219,6 +219,9 @@ BOOST_DECIMAL_EXPORT class decimal64 final // We can super easily combine this into a single op friend constexpr auto not_finite(decimal64 rhs) noexcept -> bool; + template + friend constexpr auto equality_impl(DecimalType lhs, DecimalType rhs) noexcept -> bool; + public: // 3.2.3.1 construct/copy/destroy constexpr decimal64() noexcept = default; @@ -1622,16 +1625,7 @@ constexpr auto decimal64::operator%=(decimal64 rhs) noexcept -> decimal64& constexpr auto operator==(decimal64 lhs, decimal64 rhs) noexcept -> bool { - #ifndef BOOST_DECIMAL_FAST_MATH - // Check for IEEE requirement that nan != nan - if (isnan(lhs) || isnan(rhs)) - { - return false; - } - #endif - - return equal_parts_impl(lhs.full_significand(), lhs.biased_exponent(), lhs.isneg(), - rhs.full_significand(), rhs.biased_exponent(), rhs.isneg()); + return equality_impl(lhs, rhs); } template diff --git a/include/boost/decimal/detail/comparison.hpp b/include/boost/decimal/detail/comparison.hpp index 091b81501..0d8721106 100644 --- a/include/boost/decimal/detail/comparison.hpp +++ b/include/boost/decimal/detail/comparison.hpp @@ -24,6 +24,50 @@ namespace boost { namespace decimal { +template +BOOST_DECIMAL_FORCE_INLINE constexpr auto equality_impl(DecimalType lhs, DecimalType rhs) noexcept -> bool +{ + using comp_type = typename DecimalType::significand_type; + + // Step 1: Check for NANs per IEEE 754 + #ifndef BOOST_DECIMAL_FAST_MATH + if (isnan(lhs) || isnan(rhs)) + { + return false; + } + #endif + + // Step 3: Check signs + const auto lhs_neg {lhs.isneg()}; + const auto rhs_neg {rhs.isneg()}; + + if (lhs_neg != rhs_neg) + { + return false; + } + + // Step 4: Check the exponents + // If the difference is greater than we can represent in the significand than we can assume they are different + const auto lhs_exp {lhs.biased_exponent()}; + const auto rhs_exp {rhs.biased_exponent()}; + + const auto delta_exp {lhs_exp - rhs_exp}; + + if (delta_exp > detail::precision_v || delta_exp < -detail::precision_v) + { + return false; + } + + // Step 5: Normalize the significand and compare + auto lhs_sig {lhs.full_significand()}; + auto rhs_sig {rhs.full_significand()}; + + delta_exp >= 0 ? lhs_sig *= detail::pow10(static_cast(delta_exp)) : + rhs_sig *= detail::pow10(static_cast(-delta_exp)); + + return lhs_sig == rhs_sig; +} + template constexpr auto equal_parts_impl(T1 lhs_sig, U1 lhs_exp, bool lhs_sign, From 738618c61da3c7fb42a9df39e2998189f5f3e852 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 20 Sep 2024 16:27:09 -0400 Subject: [PATCH 2/3] Change d32 less than implementation --- include/boost/decimal/decimal32.hpp | 15 +++-- include/boost/decimal/detail/comparison.hpp | 64 +++++++++++++++++++++ 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/include/boost/decimal/decimal32.hpp b/include/boost/decimal/decimal32.hpp index a47f04529..febaa9411 100644 --- a/include/boost/decimal/decimal32.hpp +++ b/include/boost/decimal/decimal32.hpp @@ -215,6 +215,9 @@ BOOST_DECIMAL_EXPORT class decimal32 final // NOLINT(cppcoreguidelines-special-m template friend constexpr auto equality_impl(DecimalType lhs, DecimalType rhs) noexcept -> bool; + template + friend constexpr auto sequential_less_impl(DecimalType lhs, DecimalType rhs) noexcept -> bool; + public: // 3.2.2.1 construct/copy/destroy: constexpr decimal32() noexcept = default; @@ -1111,8 +1114,7 @@ constexpr auto operator<(decimal32 lhs, decimal32 rhs) noexcept -> bool } #endif - return less_parts_impl(lhs.full_significand(), lhs.biased_exponent(), lhs.isneg(), - rhs.full_significand(), rhs.biased_exponent(), rhs.isneg()); + return sequential_less_impl(lhs, rhs); } template @@ -1156,8 +1158,7 @@ constexpr auto operator<=(decimal32 lhs, decimal32 rhs) noexcept -> bool } #endif - return !less_parts_impl(rhs.full_significand(), rhs.biased_exponent(), rhs.isneg(), - lhs.full_significand(), lhs.biased_exponent(), lhs.isneg()); + return !sequential_less_impl(rhs, lhs); } template @@ -1222,8 +1223,7 @@ constexpr auto operator>(decimal32 lhs, decimal32 rhs) noexcept -> bool } #endif - return less_parts_impl(rhs.full_significand(), rhs.biased_exponent(), rhs.isneg(), - lhs.full_significand(), lhs.biased_exponent(), lhs.isneg()); + return sequential_less_impl(rhs, lhs); } template @@ -1263,8 +1263,7 @@ constexpr auto operator>=(decimal32 lhs, decimal32 rhs) noexcept -> bool } #endif - return !less_parts_impl(lhs.full_significand(), lhs.biased_exponent(), lhs.isneg(), - rhs.full_significand(), rhs.biased_exponent(), rhs.isneg()); + return !sequential_less_impl(lhs, rhs); } template diff --git a/include/boost/decimal/detail/comparison.hpp b/include/boost/decimal/detail/comparison.hpp index 0d8721106..bb8b6f3d2 100644 --- a/include/boost/decimal/detail/comparison.hpp +++ b/include/boost/decimal/detail/comparison.hpp @@ -241,6 +241,70 @@ BOOST_DECIMAL_FORCE_INLINE constexpr auto fast_type_less_parts_impl(T lhs_sig, U return lhs_sign ? lhs_sig > rhs_sig : lhs_sig < rhs_sig; } +template +constexpr auto sequential_less_impl(DecimalType lhs, DecimalType rhs) noexcept -> bool +{ + using comp_type = std::uint_fast64_t; + + // Step 1: Handle our non-finite values in their own calling functions + + // Step 2: Check if they are bitwise equal: + /* + if (lhs.bits_ == rhs.bits_) + { + return false; + } + */ + + // Step 3: Decode and compare signs first: + const auto lhs_sign {lhs.isneg()}; + const auto rhs_sign {rhs.isneg()}; + + if (lhs_sign != rhs_sign) + { + return lhs_sign; + } + + // Step 4: Decode the significand and do a trivial comp + auto lhs_sig {static_cast(lhs.full_significand())}; + auto rhs_sig {static_cast(rhs.full_significand())}; + if (lhs_sig == static_cast(0) || rhs_sig == static_cast(0)) + { + return (lhs_sig == rhs_sig) ? false : (lhs_sig == static_cast(0) ? !rhs_sign : lhs_sign); + } + + // Step 5: Decode the exponent and see if we can even compare the significands + auto lhs_exp {lhs.biased_exponent()}; + auto rhs_exp {rhs.biased_exponent()}; + + const auto delta_exp {lhs_exp - rhs_exp}; + constexpr auto max_delta_diff {std::numeric_limits::digits10 - detail::precision_v}; + + if (delta_exp > max_delta_diff || delta_exp < -max_delta_diff) + { + return rhs_sign ? rhs_exp < lhs_exp : rhs_exp > lhs_exp; + } + + // Step 6: Approximate normalization if we need to and then get the answer + if (delta_exp >= 0) + { + lhs_sig *= detail::pow10(static_cast(delta_exp)); + lhs_exp -= delta_exp; + } + else + { + rhs_sig *= detail::pow10(static_cast(-delta_exp)); + rhs_exp += delta_exp; + } + + if (lhs_exp != rhs_exp) + { + return lhs_sign ? lhs_exp > rhs_exp : lhs_exp < rhs_exp; + } + + return lhs_sign ? lhs_sig > rhs_sig : lhs_sig < rhs_sig; +} + template constexpr auto less_parts_impl(T1 lhs_sig, U1 lhs_exp, bool lhs_sign, From 7f63f4f203560219ad1eee3cfad059b7e90a6f2c Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 20 Sep 2024 16:43:01 -0400 Subject: [PATCH 3/3] Revert changes to decimal64 --- include/boost/decimal/decimal64.hpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/include/boost/decimal/decimal64.hpp b/include/boost/decimal/decimal64.hpp index 8bf8b3164..019f0fbe2 100644 --- a/include/boost/decimal/decimal64.hpp +++ b/include/boost/decimal/decimal64.hpp @@ -1625,7 +1625,16 @@ constexpr auto decimal64::operator%=(decimal64 rhs) noexcept -> decimal64& constexpr auto operator==(decimal64 lhs, decimal64 rhs) noexcept -> bool { - return equality_impl(lhs, rhs); + #ifndef BOOST_DECIMAL_FAST_MATH + // Check for IEEE requirement that nan != nan + if (isnan(lhs) || isnan(rhs)) + { + return false; + } + #endif + + return equal_parts_impl(lhs.full_significand(), lhs.biased_exponent(), lhs.isneg(), + rhs.full_significand(), rhs.biased_exponent(), rhs.isneg()); } template