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

Try first cut at issue 368 #371

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 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
241 changes: 211 additions & 30 deletions include/boost/multiprecision/cpp_dec_float.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,141 @@ class cpp_dec_float
ar& boost::make_nvp("precision", prec_elem);
}

void eval_round_self()
{
const bool needs_rounding = (((isfinite)() == true) && (iszero() == false));

if(needs_rounding)
{
using local_size_type = typename array_type::size_type;
using local_limb_type = typename array_type::value_type;

std::int32_t digits_limb_0 = 0;

local_limb_type tmp_limb_0 = data[0U];

// Manually count the number of base-10 digits on the zero'th limb.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the bit that caused me to assume that correct round was next to impossible to do efficiently in this type, but maybe it's not so bad? I can't help but wonder if there are more efficient implementations - maybe binary search comparisons? Or can we even do table lookup based on the most-significant-bit? Just thinking out loud here....

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the bit that caused me to assume that correct round was next to impossible to do efficiently in this type

Well, it sure is proving to be unruly. But there are some new efficiency provisions.

  • There was an existing binary search which had calculated the number of base-10 digits in the leading limb. This is used instead of a manual counting loop now.
  • For the power-of-10, a special non-constexpr table lookup has also been created for quick calculations of the power(s) of ten in similar matters.

while(tmp_limb_0 > 0U)
{
tmp_limb_0 /= 10U;

++digits_limb_0;
}

constexpr std::int32_t local_max_digits10 =
std::int32_t
(
cpp_dec_float_digits10 + 4
);

const std::int32_t digits_limb_1_to_n = local_max_digits10 - digits_limb_0;

// Find the index of the element that contains the least-significant base-10 digit.
std::int32_t least_digit_idx = std::int32_t( (digits_limb_1_to_n / cpp_dec_float_elem_digits10)
+ ((digits_limb_1_to_n % cpp_dec_float_elem_digits10) != 0));

// Set the index of the element that contains the rounding base-10 digit.
std::int32_t round_digit_idx =
std::int32_t(((digits_limb_1_to_n % cpp_dec_float_elem_digits10) != 0)
? least_digit_idx
: least_digit_idx + 1);

// Find the base-10 order (position) of the least-significant base-10 digit.
std::int32_t least_digit_pos =
(std::int32_t(digits_limb_1_to_n % cpp_dec_float_elem_digits10) != 0)
? std::int32_t
(
cpp_dec_float_elem_digits10
- std::int32_t(digits_limb_1_to_n % cpp_dec_float_elem_digits10)
)
: 0;

// Find the base-10 order (position) of the rounding base-10 digit.
std::int32_t round_digit_pos =
std::int32_t((least_digit_pos != 0) ? least_digit_pos - 1 : cpp_dec_float_elem_digits10 - 1);

// Get the value of the least-significant base-10 digit.
const std::uint8_t least_digit_value =
detail::digit_at_pos_in_limb
(
data[local_size_type(least_digit_idx)],
unsigned(least_digit_pos)
);

// TBD: Remove unused debug-helper variable.
static_cast<void>(least_digit_value);

// Get the value of the rounding base-10 digit.
const std::uint8_t round_digit_value =
detail::digit_at_pos_in_limb
(
data[local_size_type(round_digit_idx)],
unsigned(round_digit_pos)
);

local_limb_type least_digit_p10 = detail::pow10_maker(std::uint32_t(least_digit_pos));
local_limb_type round_digit_p10 = detail::pow10_maker(std::uint32_t(round_digit_pos));

// TBD: Remove unused debug-helper variable.
static_cast<void>(round_digit_p10);

// Clear the lower base-10 digits of the rounded element.
data[local_size_type(least_digit_idx)] -= local_limb_type(data[local_size_type(least_digit_idx)] % least_digit_p10);

// Clear the lower base-10 limbs.
if(local_size_type(least_digit_idx + 1) < data.size() - 1U)
{
std::fill(data.begin() + local_size_type(least_digit_idx + 1), data.end(), local_limb_type(0U));
}

// Perform round-to-nearest with no tie-breaking whatsoever.
if(round_digit_value >= 5U)
{
data[local_size_type(least_digit_idx)] += least_digit_p10;

// There is a carry from rounding up.
std::uint_fast8_t carry_out =
((data[local_size_type(least_digit_idx)] >= cpp_dec_float_elem_mask)
? static_cast<std::uint_fast8_t>(1U)
: static_cast<std::uint_fast8_t>(0U));

// Propogate the carry into the limbs of higher significance as needed.
if(carry_out != 0U)
{
data[local_size_type(least_digit_idx)] -= cpp_dec_float_elem_mask;

--least_digit_idx;

for( ; least_digit_idx >= 0 && (carry_out != 0U); --least_digit_idx)
{
const local_limb_type tt = local_limb_type(data[local_size_type(least_digit_idx)] + local_limb_type(carry_out));

carry_out = ((tt >= cpp_dec_float_elem_mask) ? static_cast<std::uint_fast8_t>(1U)
: static_cast<std::uint_fast8_t>(0U));

data[local_size_type(least_digit_idx)] =
static_cast<local_limb_type>(tt - ((carry_out != 0U) ? cpp_dec_float_elem_mask
: static_cast<local_limb_type>(0U)));
}

if((least_digit_idx < 0) && (carry_out != 0U))
{
// In rare cases, propagation of the carry reaches the zero'th limb
// of highest significance, and we must shift the data, create a new limb
// with the carry value of 1 and adjust the exponent accordingly.
std::copy_backward(data.cbegin(),
data.cend() - 1,
data.end());

data[0U] = carry_out;

exp = static_cast<exponent_type>(exp + static_cast<exponent_type>(cpp_dec_float_elem_digits10));
}
}
}
}
}

private:
static bool data_elem_is_non_zero_predicate(const std::uint32_t& d) { return (d != static_cast<std::uint32_t>(0u)); }
static bool data_elem_is_non_nine_predicate(const std::uint32_t& d) { return (d != static_cast<std::uint32_t>(cpp_dec_float::cpp_dec_float_elem_mask - 1)); }
Expand Down Expand Up @@ -1643,10 +1778,15 @@ boost::long_long_type cpp_dec_float<Digits10, ExponentType, Allocator>::extract_
else
{
// Extract the data into an boost::ulong_long_type value.
cpp_dec_float<Digits10, ExponentType, Allocator> xn(extract_integer_part());
cpp_dec_float<Digits10, ExponentType, Allocator> xn(*this);

xn.eval_round_self();

if (xn.isneg())
xn.negate();

xn = xn.extract_integer_part();

val = static_cast<boost::ulong_long_type>(xn.data[0]);

const std::int32_t imax = (std::min)(static_cast<std::int32_t>(static_cast<std::int32_t>(xn.exp) / cpp_dec_float_elem_digits10), static_cast<std::int32_t>(cpp_dec_float_elem_number - static_cast<std::int32_t>(1)));
Expand Down Expand Up @@ -1688,35 +1828,40 @@ boost::ulong_long_type cpp_dec_float<Digits10, ExponentType, Allocator>::extract
{
return static_cast<boost::ulong_long_type>(extract_signed_long_long());
}

if (exp < static_cast<exponent_type>(0))
else if (exp < static_cast<exponent_type>(0))
{
return static_cast<boost::ulong_long_type>(0u);
}

const cpp_dec_float<Digits10, ExponentType, Allocator> xn(extract_integer_part());

boost::ulong_long_type val;

if (xn.compare(ulong_long_max()) > 0)
{
return (std::numeric_limits<boost::ulong_long_type>::max)();
}
else
{
// Extract the data into an boost::ulong_long_type value.
val = static_cast<boost::ulong_long_type>(xn.data[0]);
cpp_dec_float<Digits10, ExponentType, Allocator> xn(*this);

const std::int32_t imax = (std::min)(static_cast<std::int32_t>(static_cast<std::int32_t>(xn.exp) / cpp_dec_float_elem_digits10), static_cast<std::int32_t>(cpp_dec_float_elem_number - static_cast<std::int32_t>(1)));
xn.eval_round_self();

for (std::int32_t i = static_cast<std::int32_t>(1); i <= imax; i++)
xn = xn.extract_integer_part();

boost::ulong_long_type val;

if (xn.compare(ulong_long_max()) > 0)
{
val *= static_cast<boost::ulong_long_type>(cpp_dec_float_elem_mask);
val += static_cast<boost::ulong_long_type>(xn.data[i]);
return (std::numeric_limits<boost::ulong_long_type>::max)();
}
else
{
// Extract the data into an boost::ulong_long_type value.
val = static_cast<boost::ulong_long_type>(xn.data[0]);

const std::int32_t imax = (std::min)(static_cast<std::int32_t>(static_cast<std::int32_t>(xn.exp) / cpp_dec_float_elem_digits10), static_cast<std::int32_t>(cpp_dec_float_elem_number - static_cast<std::int32_t>(1)));

for (std::int32_t i = static_cast<std::int32_t>(1); i <= imax; i++)
{
val *= static_cast<boost::ulong_long_type>(cpp_dec_float_elem_mask);
val += static_cast<boost::ulong_long_type>(xn.data[i]);
}
}
}

return val;
return val;
}
}

template <unsigned Digits10, class ExponentType, class Allocator>
Expand Down Expand Up @@ -3091,37 +3236,73 @@ template <unsigned Digits10, class ExponentType, class Allocator>
inline void eval_floor(cpp_dec_float<Digits10, ExponentType, Allocator>& result, const cpp_dec_float<Digits10, ExponentType, Allocator>& x)
{
result = x;
if (!(x.isfinite)() || x.isint())

if((x.isfinite)() == false)
{
if ((x.isnan)())
errno = EDOM;
return;
}
else
{
result.eval_round_self();

if (x.isneg())
result -= cpp_dec_float<Digits10, ExponentType, Allocator>::one();
result = result.extract_integer_part();
if(result.isint())
{
;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if(result.isint())
?

Indeed. These sequences have been simlified in recent push(es).

}
else
{
if(result.isneg())
{
result -= cpp_dec_float<Digits10, ExponentType, Allocator>::one();
}
else
{
;
}

result = result.extract_integer_part();
}
}
}

template <unsigned Digits10, class ExponentType, class Allocator>
inline void eval_ceil(cpp_dec_float<Digits10, ExponentType, Allocator>& result, const cpp_dec_float<Digits10, ExponentType, Allocator>& x)
{
result = x;
if (!(x.isfinite)() || x.isint())

if ((x.isfinite)() == false)
{
if ((x.isnan)())
errno = EDOM;
return;
}
else
{
result.eval_round_self();

if(result.isint())
{
;
}
else
{
if(result.isneg())
{
}
else
{
result += cpp_dec_float<Digits10, ExponentType, Allocator>::one();
}

if (!x.isneg())
result += cpp_dec_float<Digits10, ExponentType, Allocator>::one();
result = result.extract_integer_part();
result = result.extract_integer_part();
}
}
}

template <unsigned Digits10, class ExponentType, class Allocator>
inline void eval_trunc(cpp_dec_float<Digits10, ExponentType, Allocator>& result, const cpp_dec_float<Digits10, ExponentType, Allocator>& x)
{

if (x.isint() || !(x.isfinite)())
{
result = x;
Expand Down
8 changes: 8 additions & 0 deletions include/boost/multiprecision/detail/tables.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ constexpr std::uint32_t pow10_maker(std::uint32_t n)
return ((n == UINT32_C(0)) ? UINT32_C(1) : pow10_maker(n - UINT32_C(1)) * UINT32_C(10));
}

template<typename LimbType>
inline std::uint8_t digit_at_pos_in_limb(const LimbType u, const unsigned pos)
{
LimbType p10 = static_cast<LimbType>(pow10_maker(pos));

return std::uint8_t(LimbType(u / p10) % LimbType(10U));
}

}}}} // namespace boost::multiprecision::backends::detail

#endif // BOOST_MP_DETAIL_TABLES_HPP
1 change: 1 addition & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,7 @@ test-suite misc :
[ run git_issue_265.cpp : : : [ check-target-builds ../config//has_mpfr : <source>gmp <source>mpfr : <build>no ] ]
[ run git_issue_277.cpp ]
[ run git_issue_313.cpp ]
[ run git_issue_368.cpp ]
[ compile git_issue_98.cpp :
[ check-target-builds ../config//has_float128 : <define>TEST_FLOAT128 <source>quadmath : ]
[ check-target-builds ../config//has_gmp : <define>TEST_GMP <source>gmp : ]
Expand Down
63 changes: 63 additions & 0 deletions test/git_issue_368.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright 2021 Christopher Kormanyos. Distributed under the Boost
// Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/multiprecision/cpp_dec_float.hpp>

#include "test.hpp"

namespace local
{
template<typename DecimalType>
void test_ceil_rounding()
{
using decimal_type = DecimalType;

bool result_is_ok = true;

for(std::uint32_t lo_index = 101U; lo_index < 1010U; lo_index += 7U)
{
for(std::uint32_t hi_index = 10001U; hi_index < 100010U; hi_index += 17U)
{
const std::uint32_t lo_hi = lo_index * hi_index;

const decimal_type a = decimal_type { lo_hi } / decimal_type { lo_index };
const decimal_type b = ceil(a);

const std::string str_a = boost::lexical_cast<std::string>(a);
const std::string str_b = boost::lexical_cast<std::string>(b);

result_is_ok &= (str_a == str_b);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questions: why are we comparing strings rather than values, and should we also test inputs which are not integer? Negative values as well, plus floor too I suspect ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we comparing strings rather than values,

In this PR, there is rounding on floor, ceil and cast-to-int only. I removed the use of strings. But the comparison with, say, the result of floor or ceil is only true if the value being compared is casted to int prior to comparison. This is now done without strings in the candidate code of the PR's test file.

we also test inputs which are not integer

It seems like there are already-existing rounding tests which do this.

Negative values as well, plus floor too I suspect

Understood. Done in resent push(es).


if(result_is_ok == false)
{
break;
}
}
}

BOOST_CHECK_EQUAL(result_is_ok, true);
}
}

int main(int, char**)
{
bool result_is_ok = true;

{
using decimal = boost::multiprecision::number<boost::multiprecision::cpp_dec_float<10>,
boost::multiprecision::et_off>;

local::test_ceil_rounding<decimal>();
}

{
using decimal = boost::multiprecision::number<boost::multiprecision::cpp_dec_float<12>,
boost::multiprecision::et_off>;

local::test_ceil_rounding<decimal>();
}

return boost::report_errors();
}