Skip to content

Commit 22a0cc3

Browse files
authored
Merge pull request #1146 from cppalliance/1105v2
Fix handling of denorm next after
2 parents 7cfb824 + 8d2bf74 commit 22a0cc3

File tree

5 files changed

+82
-6
lines changed

5 files changed

+82
-6
lines changed

include/boost/decimal/decimal128_t.hpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ constexpr decimal128_t::decimal128_t(T1 coeff, T2 exp, bool sign) noexcept
745745
auto biased_exp {static_cast<int>(exp + detail::bias_v<decimal128_t>)};
746746
BOOST_DECIMAL_IF_CONSTEXPR (sizeof(T1) >= sizeof(significand_type))
747747
{
748-
if (coeff > detail::d128_max_significand_value || biased_exp < 0)
748+
if (coeff > detail::d128_max_significand_value || biased_exp < -(detail::precision_v<decimal128_t> - 1))
749749
{
750750
coeff_digits = detail::coefficient_rounding<decimal128_t>(coeff, exp, biased_exp, sign, detail::num_digits(coeff));
751751
}
@@ -788,6 +788,19 @@ constexpr decimal128_t::decimal128_t(T1 coeff, T2 exp, bool sign) noexcept
788788
reduced_coeff *= detail::pow10(static_cast<significand_type>(digit_delta));
789789
*this = decimal128_t(reduced_coeff, exp, sign);
790790
}
791+
else if (coeff_digits + biased_exp <= detail::precision_v<decimal128_t>)
792+
{
793+
// Handle the case of sub-normals that don't need further rounding
794+
bits_.high = sign ? detail::d128_sign_mask : UINT64_C(0); // Reset the sign bit
795+
const auto zeros {detail::remove_trailing_zeros(reduced_coeff)};
796+
biased_exp += static_cast<int>(zeros.number_of_removed_zeros);
797+
reduced_coeff = zeros.trimmed_number;
798+
if (biased_exp > 0)
799+
{
800+
reduced_coeff *= detail::pow10(static_cast<significand_type>(biased_exp));
801+
}
802+
bits_ |= reduced_coeff;
803+
}
791804
else if (digit_delta < 0 && coeff_digits - digit_delta <= detail::precision_v<decimal128_t>)
792805
{
793806
const auto offset {detail::precision_v<decimal128_t> - coeff_digits};

include/boost/decimal/decimal32_t.hpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ constexpr decimal32_t::decimal32_t(T1 coeff, T2 exp, bool sign) noexcept // NOLI
653653
// Only count the number of digits if we absolutely have to
654654
int coeff_digits {-1};
655655
auto biased_exp {static_cast<int>(exp + detail::bias)};
656-
if (coeff > detail::d32_max_significand_value || biased_exp < 0)
656+
if (coeff > detail::d32_max_significand_value || biased_exp < -(detail::precision_v<decimal32_t> - 1))
657657
{
658658
coeff_digits = detail::coefficient_rounding<decimal32_t>(coeff, exp, biased_exp, sign, detail::num_digits(coeff));
659659
}
@@ -710,6 +710,19 @@ constexpr decimal32_t::decimal32_t(T1 coeff, T2 exp, bool sign) noexcept // NOLI
710710
reduced_coeff *= detail::pow10(static_cast<significand_type>(digit_delta));
711711
*this = decimal32_t(reduced_coeff, exp, sign);
712712
}
713+
else if (coeff_digits + biased_exp <= detail::precision)
714+
{
715+
// Handle the case of sub-normals that don't need further rounding
716+
bits_ = sign ? detail::d32_sign_mask : UINT32_C(0); // Reset the sign bit
717+
const auto zeros {detail::remove_trailing_zeros(reduced_coeff)};
718+
biased_exp += static_cast<int>(zeros.number_of_removed_zeros);
719+
reduced_coeff = zeros.trimmed_number;
720+
if (biased_exp > 0)
721+
{
722+
reduced_coeff *= detail::pow10(static_cast<significand_type>(biased_exp));
723+
}
724+
bits_ |= reduced_coeff;
725+
}
713726
else if (digit_delta < 0 && coeff_digits - digit_delta <= detail::precision)
714727
{
715728
// We can expand the coefficient to use the maximum number of digits

include/boost/decimal/decimal64_t.hpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ constexpr decimal64_t::decimal64_t(T1 coeff, T2 exp, bool sign) noexcept
647647
// If the coeff is not in range, make it so
648648
int coeff_digits {-1};
649649
auto biased_exp {static_cast<int>(exp) + detail::bias_v<decimal64_t>};
650-
if (coeff > detail::d64_max_significand_value || biased_exp < 0)
650+
if (coeff > detail::d64_max_significand_value || biased_exp < -(detail::precision_v<decimal64_t> - 1))
651651
{
652652
coeff_digits = detail::coefficient_rounding<decimal64_t>(coeff, exp, biased_exp, sign, detail::num_digits(coeff));
653653
}
@@ -704,6 +704,19 @@ constexpr decimal64_t::decimal64_t(T1 coeff, T2 exp, bool sign) noexcept
704704
reduced_coeff *= detail::pow10(static_cast<significand_type>(digit_delta));
705705
*this = decimal64_t(reduced_coeff, exp, sign);
706706
}
707+
else if (coeff_digits + biased_exp <= detail::precision_v<decimal64_t>)
708+
{
709+
// Handle the case of sub-normals that don't need further rounding
710+
bits_ = sign ? detail::d64_sign_mask : UINT64_C(0); // Reset the sign bit
711+
const auto zeros {detail::remove_trailing_zeros(reduced_coeff)};
712+
biased_exp += static_cast<int>(zeros.number_of_removed_zeros);
713+
reduced_coeff = zeros.trimmed_number;
714+
if (biased_exp > 0)
715+
{
716+
reduced_coeff *= detail::pow10(static_cast<significand_type>(biased_exp));
717+
}
718+
bits_ |= reduced_coeff;
719+
}
707720
else if (digit_delta < 0 && coeff_digits - digit_delta <= detail::precision_v<decimal64_t>)
708721
{
709722
const auto offset {detail::precision_v<decimal64_t> - coeff_digits};

include/boost/decimal/detail/cmath/next.hpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,24 @@ constexpr auto nextafter_impl(const DecimalType val, const bool direction) noexc
5353

5454
if (!isnormal(val))
5555
{
56-
// Not to make sure that denorms aren't normalized
56+
// Denorms need separate handling
5757
sig = removed_zeros.trimmed_number;
5858
exp += static_cast<int>(removed_zeros.number_of_removed_zeros);
59+
60+
if (removed_zeros.number_of_removed_zeros > 0)
61+
{
62+
// We need to shift an add
63+
// 1 -> 11 instead of 2 since 11e-101 < 2e-100 starting at 1e-100
64+
sig *= 10U;
65+
++sig;
66+
--exp;
67+
}
68+
else
69+
{
70+
++sig;
71+
}
72+
73+
return DecimalType{sig, exp, is_neg};
5974
}
6075

6176
if (direction)

test/github_issue_1105.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,29 @@ void test()
4545

4646
const auto zero_next {nextafter(zero, one)};
4747
BOOST_TEST_EQ(zero_next, std::numeric_limits<T>::denorm_min());
48+
}
49+
50+
// Per IEEE 754 nextafter is allowed to break cohort
51+
void test_non_preserving()
52+
{
53+
const auto val = decimal32_t{"1e-100"};
54+
const auto two_val = decimal32_t{2, boost::decimal::detail::etiny_v<decimal32_t>};
55+
const auto one = decimal32_t{"1e0"};
56+
const auto next = nextafter(val,one);
57+
const auto between = decimal32_t{"11e-101"};
4858

49-
const auto two_next_zero {nextafter(zero_next, one)};
50-
BOOST_TEST_EQ(two_next_zero, T(2, detail::etiny_v<T>));
59+
BOOST_TEST_LE(val, between);
60+
BOOST_TEST_EQ(next, between);
61+
BOOST_TEST_LE(two_val, next);
62+
63+
const auto nines_value = decimal32_t{"99e-101"};
64+
const auto next_nines_res = decimal32_t{"991e-102"};
65+
const auto res = nextafter(nines_value, one);
66+
BOOST_TEST_EQ(res, next_nines_res);
67+
68+
const auto wrap_value = decimal32_t{"9999999e-107"};
69+
const auto next_after_wrap = nextafter(wrap_value, one);
70+
BOOST_TEST_GT(next_after_wrap, wrap_value);
5171
}
5272

5373
int main()
@@ -56,5 +76,7 @@ int main()
5676
test<decimal64_t>();
5777
test<decimal128_t>();
5878

79+
test_non_preserving();
80+
5981
return boost::report_errors();
6082
}

0 commit comments

Comments
 (0)