diff --git a/src/ripple/basics/Number.h b/src/ripple/basics/Number.h index ead0e4321..cc009fa2e 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 10834f08e..0690be077 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 b5425a7bc..d7bd82648 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