diff --git a/src/ripple/basics/Number.h b/src/ripple/basics/Number.h index 3832a8495..92b99bf01 100644 --- a/src/ripple/basics/Number.h +++ b/src/ripple/basics/Number.h @@ -37,7 +37,7 @@ class Number { using rep = std::int64_t; rep mantissa_{0}; - int exponent_{-2'147'483'648}; + int exponent_{std::numeric_limits::lowest()}; public: struct unchecked @@ -45,7 +45,7 @@ public: explicit unchecked() = default; }; - explicit Number() = default; + explicit constexpr Number() = default; Number(rep mantissa); explicit Number(rep mantissa, int exponent); @@ -166,7 +166,7 @@ private: constexpr static int minExponent = -32768; constexpr static int maxExponent = 32768; - class guard; + class Guard; }; inline constexpr Number::Number(rep mantissa, int exponent, unchecked) noexcept @@ -308,10 +308,10 @@ abs(Number x) noexcept } // Returns f^n -// Uses a log_2(n) number of mulitiplications +// Uses a log_2(n) number of multiplications Number -power(Number f, unsigned n); +power(Number const& f, unsigned n); // Returns f^(1/d) // Uses Newton–Raphson iterations until the result stops changing @@ -323,12 +323,12 @@ root(Number f, unsigned d); // Returns f^(n/d) Number -power(Number f, unsigned n, unsigned d); +power(Number const& f, unsigned n, unsigned d); // Return 0 if abs(x) < limit, else returns x inline constexpr Number -clip(Number const& x, Number const& limit) noexcept +squelch(Number const& x, Number const& limit) noexcept { if (abs(x) < limit) return Number{}; diff --git a/src/ripple/basics/impl/Number.cpp b/src/ripple/basics/impl/Number.cpp index e59ca5fb6..10834f08e 100644 --- a/src/ripple/basics/impl/Number.cpp +++ b/src/ripple/basics/impl/Number.cpp @@ -33,53 +33,66 @@ using uint128_t = __uint128_t; namespace ripple { -// guard +// Guard -class Number::guard +// The Guard class is used to tempoarily add extra digits of +// preicision to an operation. This enables the final result +// to be correctly rounded to the internal precision of Number. + +class Number::Guard { - std::uint64_t digits_; - std::uint8_t xbit_ : 1; - std::uint8_t sbit_ : 1; // TODO : get rid of + std::uint64_t digits_; // 16 decimal guard digits + std::uint8_t xbit_ : 1; // has a non-zero digit been shifted off the end + std::uint8_t sbit_ : 1; // the sign of the guard digits public: - explicit guard() : digits_{0}, xbit_{0}, sbit_{0} + explicit Guard() : digits_{0}, xbit_{0}, sbit_{0} { } + // set & test the sign bit void set_positive() noexcept; void set_negative() noexcept; bool is_negative() const noexcept; + + // add a digit void push(unsigned d) noexcept; + + // recover a digit unsigned pop() noexcept; + + // Indicate round direction: 1 is up, -1 is down, 0 is even + // This enables the client to round towards nearest, and on + // tie, round towards even. int round() noexcept; }; inline void -Number::guard::set_positive() noexcept +Number::Guard::set_positive() noexcept { sbit_ = 0; } inline void -Number::guard::set_negative() noexcept +Number::Guard::set_negative() noexcept { sbit_ = 1; } inline bool -Number::guard::is_negative() const noexcept +Number::Guard::is_negative() const noexcept { return sbit_ == 1; } inline void -Number::guard::push(unsigned d) noexcept +Number::Guard::push(unsigned d) noexcept { xbit_ = xbit_ || (digits_ & 0x0000'0000'0000'000F) != 0; digits_ >>= 4; @@ -87,7 +100,7 @@ Number::guard::push(unsigned d) noexcept } inline unsigned -Number::guard::pop() noexcept +Number::Guard::pop() noexcept { unsigned d = (digits_ & 0xF000'0000'0000'0000) >> 60; digits_ <<= 4; @@ -95,7 +108,7 @@ Number::guard::pop() noexcept } int -Number::guard::round() noexcept +Number::Guard::round() noexcept { if (digits_ > 0x5000'0000'0000'0000) return 1; @@ -127,10 +140,12 @@ Number::normalize() m *= 10; --exponent_; } + Guard g; while (m > maxMantissa) { if (exponent_ >= maxExponent) throw std::overflow_error("Number::normalize 1"); + g.push(m % 10); m /= 10; ++exponent_; } @@ -141,6 +156,16 @@ Number::normalize() return; } + auto r = g.round(); + if (r == 1 || (r == 0 && (mantissa_ & 1) == 1)) + { + ++mantissa_; + if (mantissa_ > maxMantissa) + { + mantissa_ /= 10; + ++exponent_; + } + } if (exponent_ > maxExponent) throw std::overflow_error("Number::normalize 2"); @@ -180,7 +205,7 @@ Number::operator+=(Number const& y) ym = -ym; yn = -1; } - guard g; + Guard g; if (xe < ye) { if (xn == -1) @@ -261,7 +286,6 @@ Number::operator+=(Number const& y) } mantissa_ = xm * xn; exponent_ = xe; - assert(isnormal()); return *this; } @@ -295,7 +319,7 @@ Number::operator*=(Number const& y) auto zm = uint128_t(xm) * uint128_t(ym); auto ze = xe + ye; auto zn = xn * yn; - guard g; + Guard g; while (zm > maxMantissa) { g.push(static_cast(zm % 10)); @@ -379,7 +403,7 @@ Number::operator rep() const { rep drops = mantissa_; int offset = exponent_; - guard g; + Guard g; if (drops != 0) { if (drops < 0) @@ -395,7 +419,7 @@ Number::operator rep() const for (; offset > 0; --offset) { if (drops > std::numeric_limits::max() / 10) - throw std::runtime_error("Number::operator rep() overflow"); + throw std::overflow_error("Number::operator rep() overflow"); drops *= 10; } auto r = g.round(); @@ -505,10 +529,10 @@ to_string(Number const& amount) } // Returns f^n -// Uses a log_2(n) number of mulitiplications +// Uses a log_2(n) number of multiplications Number -power(Number f, unsigned n) +power(Number const& f, unsigned n) { if (n == 0) return one; @@ -525,6 +549,11 @@ power(Number f, unsigned n) // Uses Newton–Raphson iterations until the result stops changing // to find the non-negative root of the polynomial g(x) = x^d - f +// This function, and power(Number f, unsigned n, unsigned d) +// treat corner cases such as 0 roots as advised by Annex F of +// the C standard, which itself is consistent with the IEEE +// floating point standards. + Number root(Number f, unsigned d) { @@ -590,10 +619,48 @@ root(Number f, unsigned d) return Number{r.mantissa(), r.exponent() + e / di}; } +Number +root2(Number f) +{ + if (f == one) + return f; + if (f < Number{}) + throw std::overflow_error("Number::root nan"); + if (f == Number{}) + return f; + + // Scale f into the range (0, 1) such that f's exponent is a multiple of d + auto e = f.exponent() + 16; + if (e % 2 != 0) + ++e; + f = Number{f.mantissa(), f.exponent() - e}; // f /= 10^e; + + // Quadratic least squares curve fit of f^(1/d) in the range [0, 1] + auto const D = 105; + auto const a0 = 18; + auto const a1 = 144; + auto const a2 = -60; + Number r = ((Number{a2} * f + Number{a1}) * f + Number{a0}) / Number{D}; + + // Newton–Raphson iteration of f^(1/2) with initial guess r + // halt when r stops changing, checking for bouncing on the last iteration + Number rm1{}; + Number rm2{}; + do + { + rm2 = rm1; + rm1 = r; + r = (r + f / r) / Number(2); + } while (r != rm1 && r != rm2); + + // return r * 10^(e/2) to reverse scaling + return Number{r.mantissa(), r.exponent() + e / 2}; +} + // Returns f^(n/d) Number -power(Number f, unsigned n, unsigned d) +power(Number const& f, unsigned n, unsigned d) { if (f == one) return f; @@ -606,9 +673,8 @@ power(Number f, unsigned n, unsigned d) return one; if (abs(f) < one) return Number{}; - if (abs(f) > one) - throw std::overflow_error("Number::power infinity"); - throw std::overflow_error("Number::power nan"); + // abs(f) > one + throw std::overflow_error("Number::power infinity"); } if (n == 0) return one; diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index b605ed434..b5425a7bc 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include namespace ripple { @@ -42,77 +44,267 @@ public: BEAST_EXPECT(z == -z); } + void + test_limits() + { + testcase("test_limits"); + bool caught = false; + try + { + Number x{10'000'000'000'000'000, 32768}; + } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); + Number x{10'000'000'000'000'000, 32767}; + BEAST_EXPECT((x == Number{1'000'000'000'000'000, 32768})); + Number z{1'000'000'000'000'000, -32769}; + BEAST_EXPECT(z == Number{}); + Number y{1'000'000'000'000'001'500, 32000}; + BEAST_EXPECT((y == Number{1'000'000'000'000'002, 32003})); + Number m{std::numeric_limits::min()}; + BEAST_EXPECT((m == Number{-9'223'372'036'854'776, 3})); + Number M{std::numeric_limits::max()}; + BEAST_EXPECT((M == Number{9'223'372'036'854'776, 3})); + caught = false; + try + { + Number q{99'999'999'999'999'999, 32767}; + } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); + } + void test_add() { testcase("test_add"); - Number x[]{ - Number{1'000'000'000'000'000, -15}, - Number{-1'000'000'000'000'000, -15}, - Number{-1'000'000'000'000'000, -15}, - Number{-6'555'555'555'555'555, -29}}; - Number y[]{ - Number{6'555'555'555'555'555, -29}, - Number{-6'555'555'555'555'555, -29}, - Number{6'555'555'555'555'555, -29}, - Number{1'000'000'000'000'000, -15}}; - Number z[]{ - Number{1'000'000'000'000'066, -15}, - Number{-1'000'000'000'000'066, -15}, - Number{-9'999'999'999'999'344, -16}, - Number{9'999'999'999'999'344, -16}}; - for (unsigned i = 0; i < std::size(x); ++i) + using Case = std::tuple; + Case c[]{ + {Number{1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'066, -15}}, + {Number{-1'000'000'000'000'000, -15}, + Number{-6'555'555'555'555'555, -29}, + Number{-1'000'000'000'000'066, -15}}, + {Number{-1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{-9'999'999'999'999'344, -16}}, + {Number{-6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'000, -15}, + Number{9'999'999'999'999'344, -16}}, + {Number{}, Number{5}, Number{5}}, + {Number{5'555'555'555'555'555, -32768}, + Number{-5'555'555'555'555'554, -32768}, + Number{0}}, + {Number{-9'999'999'999'999'999, -31}, + Number{1'000'000'000'000'000, -15}, + Number{9'999'999'999'999'990, -16}}}; + for (auto const& [x, y, z] : c) + BEAST_EXPECT(x + y == z); + bool caught = false; + try { - BEAST_EXPECT(x[i] + y[i] == z[i]); + Number{9'999'999'999'999'999, 32768} + + Number{5'000'000'000'000'000, 32767}; } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); } void test_sub() { testcase("test_sub"); - Number x[]{ - Number{1'000'000'000'000'000, -15}, - Number{6'555'555'555'555'555, -29}}; - Number y[]{ - Number{6'555'555'555'555'555, -29}, - Number{1'000'000'000'000'000, -15}}; - Number z[]{ - Number{9'999'999'999'999'344, -16}, - Number{-9'999'999'999'999'344, -16}}; - for (unsigned i = 0; i < std::size(x); ++i) + using Case = std::tuple; + Case c[]{ + {Number{1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{9'999'999'999'999'344, -16}}, + {Number{6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'000, -15}, + Number{-9'999'999'999'999'344, -16}}, + {Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'000, -15}, + Number{0}}, + {Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'001, -15}, + Number{-1'000'000'000'000'000, -30}}, + {Number{1'000'000'000'000'001, -15}, + Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'000, -30}}}; + for (auto const& [x, y, z] : c) + BEAST_EXPECT(x - y == z); + } + + void + test_mul() + { + testcase("test_mul"); + using Case = std::tuple; + Case c[]{ + {Number{7}, Number{8}, Number{56}}, + {Number{1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{2000000000000000, -15}}, + {Number{-1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{-2000000000000000, -15}}, + {Number{-1414213562373095, -15}, + Number{-1414213562373095, -15}, + Number{2000000000000000, -15}}, + {Number{3214285714285706, -15}, + Number{3111111111111119, -15}, + Number{1000000000000000, -14}}, + {Number{1000000000000000, -32768}, + Number{1000000000000000, -32768}, + Number{0}}}; + for (auto const& [x, y, z] : c) + BEAST_EXPECT(x * y == z); + bool caught = false; + try { - BEAST_EXPECT(x[i] - y[i] == z[i]); + Number{9'999'999'999'999'999, 32768} * + Number{5'000'000'000'000'000, 32767}; } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); } void test_div() { testcase("test_div"); - Number x[]{Number{1}, Number{1}, Number{0}}; - Number y[]{Number{2}, Number{10}, Number{100}}; - Number z[]{Number{5, -1}, Number{1, -1}, Number{0}}; - for (unsigned i = 0; i < std::size(x); ++i) + using Case = std::tuple; + Case c[]{ + {Number{1}, Number{2}, Number{5, -1}}, + {Number{1}, Number{10}, Number{1, -1}}, + {Number{1}, Number{-10}, Number{-1, -1}}, + {Number{0}, Number{100}, Number{0}}}; + for (auto const& [x, y, z] : c) + BEAST_EXPECT(x / y == z); + bool caught = false; + try { - BEAST_EXPECT(x[i] / y[i] == z[i]); + Number{1000000000000000, -15} / Number{0}; } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); } void test_root() { testcase("test_root"); - Number x[]{Number{2}, Number{2'000'000}, Number{2, -30}}; - unsigned y[]{2, 2, 2}; - Number z[]{ - Number{1414213562373095, -15}, - Number{1414213562373095, -12}, - Number{1414213562373095, -30}}; - for (unsigned i = 0; i < std::size(x); ++i) + using Case = std::tuple; + Case c[]{ + {Number{2}, 2, Number{1414213562373095, -15}}, + {Number{2'000'000}, 2, Number{1414213562373095, -12}}, + {Number{2, -30}, 2, Number{1414213562373095, -30}}, + {Number{-27}, 3, Number{-3}}, + {Number{1}, 5, Number{1}}, + {Number{-1}, 0, Number{1}}, + {Number{5, -1}, 0, Number{0}}, + {Number{0}, 5, Number{0}}, + {Number{5625, -4}, 2, Number{75, -2}}}; + for (auto const& [x, y, z] : c) + BEAST_EXPECT((root(x, y) == z)); + bool caught = false; + try { - BEAST_EXPECT(root(x[i], y[i]) == z[i]); + (void)root(Number{-2}, 0); } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); + caught = false; + try + { + (void)root(Number{-2}, 4); + } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); + } + + void + test_power1() + { + testcase("test_power1"); + using Case = std::tuple; + Case c[]{ + {Number{64}, 0, Number{1}}, + {Number{64}, 1, Number{64}}, + {Number{64}, 2, Number{4096}}, + {Number{-64}, 2, Number{4096}}, + {Number{64}, 3, Number{262144}}, + {Number{-64}, 3, Number{-262144}}}; + for (auto const& [x, y, z] : c) + BEAST_EXPECT((power(x, y) == z)); + } + + void + test_power2() + { + testcase("test_power2"); + using Case = std::tuple; + Case c[]{ + {Number{1}, 3, 7, Number{1}}, + {Number{-1}, 1, 0, Number{1}}, + {Number{-1, -1}, 1, 0, Number{0}}, + {Number{16}, 0, 5, Number{1}}, + {Number{34}, 3, 3, Number{34}}, + {Number{4}, 3, 2, Number{8}}}; + for (auto const& [x, n, d, z] : c) + BEAST_EXPECT((power(x, n, d) == z)); + bool caught = false; + try + { + (void)power(Number{7}, 0, 0); + } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); + caught = false; + try + { + (void)power(Number{7}, 1, 0); + } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); + caught = false; + try + { + (void)power(Number{-1, -1}, 3, 2); + } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); } void @@ -129,102 +321,149 @@ public: STAmount st = xrp; Number n = st; BEAST_EXPECT(XRPAmount{n} == xrp); + IOUAmount x0{0, 0}; + Number y0 = x0; + BEAST_EXPECT((y0 == Number{0})); + IOUAmount z0{y0}; + BEAST_EXPECT(x0 == z0); + XRPAmount xrp0{0}; + Number n0 = xrp0; + BEAST_EXPECT(n0 == Number{0}); + XRPAmount xrp1{n0}; + BEAST_EXPECT(xrp1 == xrp0); } void test_to_integer() { testcase("test_to_integer"); - Number x[]{ - Number{0}, - Number{1}, - Number{2}, - Number{3}, - Number{-1}, - Number{-2}, - Number{-3}, - Number{10}, - Number{99}, - Number{1155}, - Number{9'999'999'999'999'999, 0}, - Number{9'999'999'999'999'999, 1}, - Number{9'999'999'999'999'999, 2}, - Number{-9'999'999'999'999'999, 2}, - Number{15, -1}, - Number{14, -1}, - Number{16, -1}, - Number{25, -1}, - Number{6, -1}, - Number{5, -1}, - Number{4, -1}, - Number{-15, -1}, - Number{-14, -1}, - Number{-16, -1}, - Number{-25, -1}, - Number{-6, -1}, - Number{-5, -1}, - Number{-4, -1}}; - std::int64_t y[]{ - 0, - 1, - 2, - 3, - -1, - -2, - -3, - 10, - 99, - 1155, - 9'999'999'999'999'999, - 99'999'999'999'999'990, - 999'999'999'999'999'900, - -999'999'999'999'999'900, - 2, - 1, - 2, - 2, - 1, - 0, - 0, - -2, - -1, - -2, - -2, - -1, - 0, - 0}; - static_assert(std::size(x) == std::size(y)); - for (unsigned u = 0; u < std::size(x); ++u) + 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) { - auto j = static_cast(x[u]); - BEAST_EXPECT(j == y[u]); + auto j = static_cast(x); + BEAST_EXPECT(j == y); } + bool caught = false; + try + { + (void)static_cast(Number{9223372036854776, 3}); + } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); } void - test_clip() + test_squelch() { - testcase("test_clip"); + testcase("test_squelch"); Number limit{1, -6}; - BEAST_EXPECT((clip(Number{2, -6}, limit) == Number{2, -6})); - BEAST_EXPECT((clip(Number{1, -6}, limit) == Number{1, -6})); - BEAST_EXPECT((clip(Number{9, -7}, limit) == Number{0})); - BEAST_EXPECT((clip(Number{-2, -6}, limit) == Number{-2, -6})); - BEAST_EXPECT((clip(Number{-1, -6}, limit) == Number{-1, -6})); - BEAST_EXPECT((clip(Number{-9, -7}, limit) == Number{0})); + BEAST_EXPECT((squelch(Number{2, -6}, limit) == Number{2, -6})); + BEAST_EXPECT((squelch(Number{1, -6}, limit) == Number{1, -6})); + BEAST_EXPECT((squelch(Number{9, -7}, limit) == Number{0})); + BEAST_EXPECT((squelch(Number{-2, -6}, limit) == Number{-2, -6})); + BEAST_EXPECT((squelch(Number{-1, -6}, limit) == Number{-1, -6})); + BEAST_EXPECT((squelch(Number{-9, -7}, limit) == Number{0})); + } + + void + testToString() + { + testcase("testToString"); + BEAST_EXPECT(to_string(Number(-2, 0)) == "-2"); + BEAST_EXPECT(to_string(Number(0, 0)) == "0"); + BEAST_EXPECT(to_string(Number(2, 0)) == "2"); + BEAST_EXPECT(to_string(Number(25, -3)) == "0.025"); + BEAST_EXPECT(to_string(Number(-25, -3)) == "-0.025"); + BEAST_EXPECT(to_string(Number(25, 1)) == "250"); + BEAST_EXPECT(to_string(Number(-25, 1)) == "-250"); + BEAST_EXPECT(to_string(Number(2, 20)) == "2000000000000000e5"); + BEAST_EXPECT(to_string(Number(-2, -20)) == "-2000000000000000e-35"); + } + + void + test_relationals() + { + testcase("test_relationals"); + BEAST_EXPECT(!(Number{100} < Number{10})); + BEAST_EXPECT(Number{100} > Number{10}); + BEAST_EXPECT(Number{100} >= Number{10}); + BEAST_EXPECT(!(Number{100} <= Number{10})); + } + + void + test_stream() + { + testcase("test_stream"); + Number x{100}; + std::ostringstream os; + os << x; + BEAST_EXPECT(os.str() == to_string(x)); + } + + void + test_inc_dec() + { + testcase("test_inc_dec"); + Number x{100}; + Number y = +x; + BEAST_EXPECT(x == y); + BEAST_EXPECT(x++ == y); + BEAST_EXPECT(x == Number{101}); + BEAST_EXPECT(x-- == Number{101}); + BEAST_EXPECT(x == y); } void run() override { testZero(); + test_limits(); test_add(); test_sub(); + test_mul(); test_div(); test_root(); + test_power1(); + test_power2(); testConversions(); test_to_integer(); - test_clip(); + test_squelch(); + testToString(); + test_relationals(); + test_stream(); + test_inc_dec(); } };