Introduce rounding modes for Number:

You can set a thread-local flag to direct Number how to round
non-exact results with the syntax:

    Number::rounding_mode prev_mode = Number::setround(Number::towards_zero);

This flag will stay in effect for this thread only until another call
to setround.  The previously set rounding mode is returned.

You can also retrieve the current rounding mode with:

    Number::rounding_mode current_mode = Number::getround();

The available rounding modes are:

* to_nearest : Rounds to nearest representable value.  On tie, rounds
               to even.
* towards_zero : Rounds towards zero.
* downward : Rounds towards negative infinity.
* upward : Rounds towards positive infinity.

The default rounding mode is to_nearest.
This commit is contained in:
Howard Hinnant
2022-08-12 10:33:43 -04:00
committed by Elliot Lee
parent a82ad5ba76
commit 3f33471220
3 changed files with 226 additions and 39 deletions

View File

@@ -149,7 +149,17 @@ public:
return os << to_string(x); return os << to_string(x);
} }
// Thread local rounding control. Default is to_nearest
enum rounding_mode { to_nearest, towards_zero, downward, upward };
static rounding_mode
getround();
// Returns previously set mode
static rounding_mode
setround(rounding_mode mode);
private: private:
static thread_local rounding_mode mode_;
void void
normalize(); normalize();
constexpr bool constexpr bool
@@ -308,6 +318,9 @@ power(Number const& f, unsigned n);
Number Number
root(Number f, unsigned d); root(Number f, unsigned d);
Number
root2(Number f);
// Returns f^(n/d) // Returns f^(n/d)
Number Number

View File

@@ -23,6 +23,7 @@
#include <numeric> #include <numeric>
#include <stdexcept> #include <stdexcept>
#include <type_traits> #include <type_traits>
#include <utility>
#ifdef _MSVC_LANG #ifdef _MSVC_LANG
#include <boost/multiprecision/cpp_int.hpp> #include <boost/multiprecision/cpp_int.hpp>
@@ -33,6 +34,20 @@ using uint128_t = __uint128_t;
namespace ripple { namespace ripple {
thread_local Number::rounding_mode Number::mode_ = Number::to_nearest;
Number::rounding_mode
Number::getround()
{
return mode_;
}
Number::rounding_mode
Number::setround(rounding_mode mode)
{
return std::exchange(mode_, mode);
}
// Guard // Guard
// The Guard class is used to tempoarily add extra digits of // The Guard class is used to tempoarily add extra digits of
@@ -107,9 +122,17 @@ Number::Guard::pop() noexcept
return d; return d;
} }
// Returns:
// -1 if Guard is less than half
// 0 if Guard is exactly half
// 1 if Guard is greater than half
int int
Number::Guard::round() noexcept Number::Guard::round() noexcept
{ {
auto mode = Number::getround();
switch (mode)
{
case to_nearest:
if (digits_ > 0x5000'0000'0000'0000) if (digits_ > 0x5000'0000'0000'0000)
return 1; return 1;
if (digits_ < 0x5000'0000'0000'0000) if (digits_ < 0x5000'0000'0000'0000)
@@ -117,6 +140,22 @@ Number::Guard::round() noexcept
if (xbit_) if (xbit_)
return 1; return 1;
return 0; return 0;
case towards_zero:
return -1;
case downward:
if (sbit_)
{
if (digits_ > 0 || xbit_)
return 1;
}
return -1;
case upward:
if (sbit_)
return -1;
if (digits_ > 0 || xbit_)
return 1;
return -1;
}
} }
// Number // Number

View File

@@ -26,6 +26,24 @@
namespace ripple { namespace ripple {
class saveNumberRoundMode
{
Number::rounding_mode mode_;
public:
~saveNumberRoundMode()
{
Number::setround(mode_);
}
explicit saveNumberRoundMode(Number::rounding_mode mode) noexcept
: mode_{mode}
{
}
saveNumberRoundMode(saveNumberRoundMode const&) = delete;
saveNumberRoundMode&
operator=(saveNumberRoundMode const&) = delete;
};
class Number_test : public beast::unit_test::suite class Number_test : public beast::unit_test::suite
{ {
public: public:
@@ -338,6 +356,8 @@ public:
{ {
testcase("test_to_integer"); testcase("test_to_integer");
using Case = std::tuple<Number, std::int64_t>; using Case = std::tuple<Number, std::int64_t>;
saveNumberRoundMode save{Number::setround(Number::to_nearest)};
{
Case c[]{ Case c[]{
{Number{0}, 0}, {Number{0}, 0},
{Number{1}, 1}, {Number{1}, 1},
@@ -372,6 +392,121 @@ public:
auto j = static_cast<std::int64_t>(x); auto j = static_cast<std::int64_t>(x);
BEAST_EXPECT(j == y); BEAST_EXPECT(j == y);
} }
}
auto prev_mode = Number::setround(Number::towards_zero);
BEAST_EXPECT(prev_mode == Number::to_nearest);
{
Case c[]{
{Number{0}, 0},
{Number{1}, 1},
{Number{2}, 2},
{Number{3}, 3},
{Number{-1}, -1},
{Number{-2}, -2},
{Number{-3}, -3},
{Number{10}, 10},
{Number{99}, 99},
{Number{1155}, 1155},
{Number{9'999'999'999'999'999, 0}, 9'999'999'999'999'999},
{Number{9'999'999'999'999'999, 1}, 99'999'999'999'999'990},
{Number{9'999'999'999'999'999, 2}, 999'999'999'999'999'900},
{Number{-9'999'999'999'999'999, 2}, -999'999'999'999'999'900},
{Number{15, -1}, 1},
{Number{14, -1}, 1},
{Number{16, -1}, 1},
{Number{25, -1}, 2},
{Number{6, -1}, 0},
{Number{5, -1}, 0},
{Number{4, -1}, 0},
{Number{-15, -1}, -1},
{Number{-14, -1}, -1},
{Number{-16, -1}, -1},
{Number{-25, -1}, -2},
{Number{-6, -1}, 0},
{Number{-5, -1}, 0},
{Number{-4, -1}, 0}};
for (auto const& [x, y] : c)
{
auto j = static_cast<std::int64_t>(x);
BEAST_EXPECT(j == y);
}
}
prev_mode = Number::setround(Number::downward);
BEAST_EXPECT(prev_mode == Number::towards_zero);
{
Case c[]{
{Number{0}, 0},
{Number{1}, 1},
{Number{2}, 2},
{Number{3}, 3},
{Number{-1}, -1},
{Number{-2}, -2},
{Number{-3}, -3},
{Number{10}, 10},
{Number{99}, 99},
{Number{1155}, 1155},
{Number{9'999'999'999'999'999, 0}, 9'999'999'999'999'999},
{Number{9'999'999'999'999'999, 1}, 99'999'999'999'999'990},
{Number{9'999'999'999'999'999, 2}, 999'999'999'999'999'900},
{Number{-9'999'999'999'999'999, 2}, -999'999'999'999'999'900},
{Number{15, -1}, 1},
{Number{14, -1}, 1},
{Number{16, -1}, 1},
{Number{25, -1}, 2},
{Number{6, -1}, 0},
{Number{5, -1}, 0},
{Number{4, -1}, 0},
{Number{-15, -1}, -2},
{Number{-14, -1}, -2},
{Number{-16, -1}, -2},
{Number{-25, -1}, -3},
{Number{-6, -1}, -1},
{Number{-5, -1}, -1},
{Number{-4, -1}, -1}};
for (auto const& [x, y] : c)
{
auto j = static_cast<std::int64_t>(x);
BEAST_EXPECT(j == y);
}
}
prev_mode = Number::setround(Number::upward);
BEAST_EXPECT(prev_mode == Number::downward);
{
Case c[]{
{Number{0}, 0},
{Number{1}, 1},
{Number{2}, 2},
{Number{3}, 3},
{Number{-1}, -1},
{Number{-2}, -2},
{Number{-3}, -3},
{Number{10}, 10},
{Number{99}, 99},
{Number{1155}, 1155},
{Number{9'999'999'999'999'999, 0}, 9'999'999'999'999'999},
{Number{9'999'999'999'999'999, 1}, 99'999'999'999'999'990},
{Number{9'999'999'999'999'999, 2}, 999'999'999'999'999'900},
{Number{-9'999'999'999'999'999, 2}, -999'999'999'999'999'900},
{Number{15, -1}, 2},
{Number{14, -1}, 2},
{Number{16, -1}, 2},
{Number{25, -1}, 3},
{Number{6, -1}, 1},
{Number{5, -1}, 1},
{Number{4, -1}, 1},
{Number{-15, -1}, -1},
{Number{-14, -1}, -1},
{Number{-16, -1}, -1},
{Number{-25, -1}, -2},
{Number{-6, -1}, 0},
{Number{-5, -1}, 0},
{Number{-4, -1}, 0}};
for (auto const& [x, y] : c)
{
auto j = static_cast<std::int64_t>(x);
BEAST_EXPECT(j == y);
}
}
bool caught = false; bool caught = false;
try try
{ {