From 3f3347122019a257fa4881c161f2f7f4e73e651b Mon Sep 17 00:00:00 2001 From: Howard Hinnant Date: Fri, 12 Aug 2022 10:33:43 -0400 Subject: [PATCH] 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. --- src/ripple/basics/Number.h | 13 ++ src/ripple/basics/impl/Number.cpp | 53 ++++++-- src/test/basics/Number_test.cpp | 199 +++++++++++++++++++++++++----- 3 files changed, 226 insertions(+), 39 deletions(-) diff --git a/src/ripple/basics/Number.h b/src/ripple/basics/Number.h index ead0e43218..cc009fa2ed 100644 --- a/src/ripple/basics/Number.h +++ b/src/ripple/basics/Number.h @@ -149,7 +149,17 @@ public: 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: + static thread_local rounding_mode mode_; + void normalize(); constexpr bool @@ -308,6 +318,9 @@ power(Number const& f, unsigned n); Number root(Number f, unsigned d); +Number +root2(Number f); + // Returns f^(n/d) Number diff --git a/src/ripple/basics/impl/Number.cpp b/src/ripple/basics/impl/Number.cpp index 10834f08e3..0690be0773 100644 --- a/src/ripple/basics/impl/Number.cpp +++ b/src/ripple/basics/impl/Number.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #ifdef _MSVC_LANG #include @@ -33,6 +34,20 @@ using uint128_t = __uint128_t; 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 // The Guard class is used to tempoarily add extra digits of @@ -107,16 +122,40 @@ Number::Guard::pop() noexcept return d; } +// Returns: +// -1 if Guard is less than half +// 0 if Guard is exactly half +// 1 if Guard is greater than half int Number::Guard::round() noexcept { - if (digits_ > 0x5000'0000'0000'0000) - return 1; - if (digits_ < 0x5000'0000'0000'0000) - return -1; - if (xbit_) - return 1; - return 0; + auto mode = Number::getround(); + switch (mode) + { + case to_nearest: + if (digits_ > 0x5000'0000'0000'0000) + return 1; + if (digits_ < 0x5000'0000'0000'0000) + return -1; + if (xbit_) + return 1; + 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 diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index b5425a7bc0..d7bd826487 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -26,6 +26,24 @@ 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 { public: @@ -338,39 +356,156 @@ public: { testcase("test_to_integer"); using Case = std::tuple; - 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}, 1}, - {Number{16, -1}, 2}, - {Number{25, -1}, 2}, - {Number{6, -1}, 1}, - {Number{5, -1}, 0}, - {Number{4, -1}, 0}, - {Number{-15, -1}, -2}, - {Number{-14, -1}, -1}, - {Number{-16, -1}, -2}, - {Number{-25, -1}, -2}, - {Number{-6, -1}, -1}, - {Number{-5, -1}, 0}, - {Number{-4, -1}, 0}}; - for (auto const& [x, y] : c) + saveNumberRoundMode save{Number::setround(Number::to_nearest)}; { - auto j = static_cast(x); - BEAST_EXPECT(j == y); + 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}, 1}, + {Number{16, -1}, 2}, + {Number{25, -1}, 2}, + {Number{6, -1}, 1}, + {Number{5, -1}, 0}, + {Number{4, -1}, 0}, + {Number{-15, -1}, -2}, + {Number{-14, -1}, -1}, + {Number{-16, -1}, -2}, + {Number{-25, -1}, -2}, + {Number{-6, -1}, -1}, + {Number{-5, -1}, 0}, + {Number{-4, -1}, 0}}; + for (auto const& [x, y] : c) + { + auto j = static_cast(x); + 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(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(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(x); + BEAST_EXPECT(j == y); + } } bool caught = false; try