diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index 5e2989823c..5221576a6f 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -52,6 +52,7 @@ using numberint = __int128_t; struct MantissaRange { + using rep = std::int64_t; using internalrep = numberint; enum mantissa_scale { small, large }; @@ -63,7 +64,7 @@ struct MantissaRange { } - internalrep min; + rep min; internalrep max; int log; mantissa_scale scale; @@ -74,11 +75,9 @@ class Number using uint128_t = numberuint; using int128_t = numberint; -public: - using rep = std::int64_t; + using rep = MantissaRange::rep; using internalrep = MantissaRange::internalrep; -private: internalrep mantissa_{0}; int exponent_{std::numeric_limits::lowest()}; @@ -93,11 +92,25 @@ public: explicit unchecked() = default; }; + // Like unchecked, normalized is used with the ctors that take an + // internalrep mantissa. Unlike unchecked, those ctors will normalize the + // value. + // Only unit tests are expected to use this class + struct normalized + { + explicit normalized() = default; + }; + explicit constexpr Number() = default; Number(rep mantissa); - explicit Number(internalrep mantissa, int exponent); - explicit constexpr Number(internalrep mantissa, int exponent, unchecked) noexcept; + explicit Number(rep mantissa, int exponent); + explicit constexpr Number( + internalrep mantissa, + int exponent, + unchecked) noexcept; + // Only unit tests are expected to use this ctor + explicit Number(internalrep mantissa, int exponent, normalized); constexpr rep mantissa() const noexcept; @@ -255,7 +268,8 @@ public: static void setMantissaScale(MantissaRange::mantissa_scale scale); - inline static internalrep + template + inline static T minMantissa() { return range_.get().min; @@ -331,16 +345,31 @@ private: bool isnormal() const noexcept; + // Copy the number, but modify the exponent by "exponentDelta". Because the + // mantissa doesn't change, the result will be "mostly" normalized, but the + // exponent could go out of range, so it will be checked. + Number + shiftExponent(int exponentDelta) const; + class Guard; }; -inline constexpr Number::Number(internalrep mantissa, int exponent, unchecked) noexcept +inline constexpr Number::Number( + internalrep mantissa, + int exponent, + unchecked) noexcept : mantissa_{mantissa}, exponent_{exponent} { } -inline Number::Number(internalrep mantissa, int exponent) - : mantissa_{mantissa}, exponent_{exponent} +inline Number::Number(internalrep mantissa, int exponent, normalized) + : Number(mantissa, exponent, unchecked{}) +{ + normalize(); +} + +inline Number::Number(rep mantissa, int exponent) + : Number(mantissa, exponent, normalized{}) { normalize(); } @@ -349,35 +378,36 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0} { } -inline -constexpr Number::rep +inline constexpr Number::rep Number::mantissa() const noexcept { auto m = mantissa_; - if (m > maxRep) + while (m > maxRep) { XRPL_ASSERT_PARTS( - m % 10 == 0, + !isnormal() || m % 10 == 0, "ripple::Number::mantissa", - "large mantissa has no remainder"); + "large normalized mantissa has no remainder"); m /= 10; } return static_cast(m); } -inline -constexpr int +inline constexpr int Number::exponent() const noexcept { - if (mantissa_ > maxRep) + auto m = mantissa_; + auto e = exponent_; + while (m > maxRep) { XRPL_ASSERT_PARTS( - mantissa_ % 10 == 0, + !isnormal() || m % 10 == 0, "ripple::Number::exponent", - "large mantissa has no remainder"); - return exponent_ + 1; + "large normalized mantissa has no remainder"); + m /= 10; + ++e; } - return exponent_; + return e; } inline constexpr Number @@ -486,6 +516,7 @@ Number::isnormal() const noexcept MantissaRange const& range = range_; auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_; return range.min <= abs_m && abs_m <= range.max && + (abs_m <= maxRep || abs_m % 10 == 0) && minExponent <= exponent_ && exponent_ <= maxExponent; } diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 7e0568569e..c71b5a90a7 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -262,13 +262,14 @@ Number::normalize( exponent_ = zero.exponent_; return; } - // When using the largeRange, m needs fit within an int64, even if + // When using the largeRange, "m" needs fit within an int64, even if // the final mantissa_ is going to end up larger to fit within the range. // Cut it down here so that the rounding will be done while it's smaller. // // Example: 9,900,000,000,000,555,555 > 9,223,372,036,854,775,808, - // so m will end up a 990,000,000,000,055,555. Then that value will be - // rounded to 990,000,000,000,055,555 or 990,000,000,000,055,556. + // so "m" will be modified to 990,000,000,000,055,555. Then that value + // will be rounded to 990,000,000,000,055,555 or + // 990,000,000,000,055,556, depending on the rounding mode. Finally, // mantissa_ will be m*10 so it fits within the range, and end up as // 9,900,000,000,000,555,550 or 9,900,000,000,000,555,560. // mantissa() will return mantissa_ / 10, and exponent() will return @@ -301,7 +302,7 @@ Number::normalize( throw std::overflow_error("Number::normalize 2"); if (mantissa_ < minMantissa) { - // When using the largeRange, the intermediate M needs fit within an + // When using the largeRange, the intermediate "m" needs fit within an // int64, but mantissa_ needs to fit within the minMantissa / // maxMantissa range. mantissa_ *= 10; @@ -323,6 +324,29 @@ Number::normalize() mantissa_, exponent_, Number::minMantissa(), Number::maxMantissa()); } +// Copy the number, but set a new exponent. Because the mantissa doesn't change, +// the result will be "mostly" normalized, but the exponent could go out of +// range. +Number +Number::shiftExponent(int exponentDelta) const +{ + XRPL_ASSERT_PARTS( + isnormal(), "ripple::Number::shiftExponent", "normalized"); + auto const newExponent = exponent_ + exponentDelta; + if (newExponent >= maxExponent) + throw std::overflow_error("Number::shiftExponent"); + if (newExponent < minExponent) + { + return Number{}; + } + Number const result{mantissa_, newExponent, unchecked{}}; + XRPL_ASSERT_PARTS( + result.isnormal(), + "ripple::Number::shiftExponent", + "result is normalized"); + return result; +} + Number& Number::operator+=(Number const& y) { @@ -442,6 +466,10 @@ Number::operator+=(Number const& y) } mantissa_ = xm * xn; exponent_ = xe; + normalize(); + XRPL_ASSERT( + isnormal() || *this == Number{}, + "ripple::Number::operator+=(Number) : result is normal"); return *this; } @@ -542,6 +570,7 @@ Number::operator*=(Number const& y) std::to_string(xe)); mantissa_ = xm * zn; exponent_ = xe; + normalize(); XRPL_ASSERT( isnormal() || *this == Number{}, "ripple::Number::operator*=(Number) : result is normal"); @@ -583,7 +612,7 @@ Number::operator/=(Number const& y) uint128_t const f = small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL; XRPL_ASSERT_PARTS( - f >= Number::minMantissa() * 10, + f >= Number::minMantissa() * 10, "Number::operator/=", "factor expected size"); @@ -841,7 +870,9 @@ root(Number f, unsigned d) return di - k2; }(); e += ex; - f = Number{f.mantissa_, f.exponent_ - e}; // f /= 10^e; + f = f.shiftExponent(-e); // f /= 10^e; + XRPL_ASSERT_PARTS( + f.isnormal(), "ripple::root(Number, unsigned)", "f is normalized"); bool neg = false; if (f < Number{}) { @@ -873,7 +904,12 @@ root(Number f, unsigned d) } while (r != rm1 && r != rm2); // return r * 10^(e/d) to reverse scaling - return Number{r.mantissa_, r.exponent_ + e / di}; + auto const result = r.shiftExponent(e / di); + XRPL_ASSERT_PARTS( + result.isnormal(), + "ripple::root(Number, unsigned)", + "result is normalized"); + return result; } Number @@ -892,7 +928,8 @@ root2(Number f) auto e = f.exponent() + Number::mantissaLog() + 1; if (e % 2 != 0) ++e; - f = Number{f.mantissa_, f.exponent_ - e}; // f /= 10^e; + f = f.shiftExponent(-e); // f /= 10^e; + XRPL_ASSERT_PARTS(f.isnormal(), "ripple::root2(Number)", "f is normalized"); // Quadratic least squares curve fit of f^(1/d) in the range [0, 1] auto const D = 105; @@ -913,7 +950,11 @@ root2(Number f) } while (r != rm1 && r != rm2); // return r * 10^(e/2) to reverse scaling - return Number{r.mantissa_, r.exponent_ + e / 2}; + auto const result = r.shiftExponent(e / 2); + XRPL_ASSERT_PARTS( + result.isnormal(), "ripple::root2(Number)", "result is normalized"); + + return result; } // Returns f^(n/d) diff --git a/src/libxrpl/protocol/IOUAmount.cpp b/src/libxrpl/protocol/IOUAmount.cpp index f69d6a3d98..ef7ba4b9b1 100644 --- a/src/libxrpl/protocol/IOUAmount.cpp +++ b/src/libxrpl/protocol/IOUAmount.cpp @@ -1,5 +1,6 @@ #include -// +// Do not remove. Forces IOUAmount.h to stay first, to verify it can compile +// without any hidden dependencies #include #include #include diff --git a/src/libxrpl/protocol/Rules.cpp b/src/libxrpl/protocol/Rules.cpp index 344ed61f0b..7543345add 100644 --- a/src/libxrpl/protocol/Rules.cpp +++ b/src/libxrpl/protocol/Rules.cpp @@ -1,5 +1,6 @@ #include -// +// Do not remove. Forces Rules.h to stay first, to verify it can compile +// without any hidden dependencies #include #include #include diff --git a/src/libxrpl/protocol/STNumber.cpp b/src/libxrpl/protocol/STNumber.cpp index 1910b81409..eee35737f5 100644 --- a/src/libxrpl/protocol/STNumber.cpp +++ b/src/libxrpl/protocol/STNumber.cpp @@ -50,27 +50,16 @@ STNumber::add(Serializer& s) const XRPL_ASSERT( getFName().fieldType == getSType(), "ripple::STNumber::add : field type match"); - if (value_.mantissa() <= std::numeric_limits::max() && - value_.mantissa() >= std::numeric_limits::min()) - { - // If the mantissa fits in the range of std::int64_t, write it directly. - // This preserves the maximum available precision. - // With the small range, all numbers should be written this way. With - // the large range, it's likely that most numbers will be written this - // way. - s.add64(static_cast(value_.mantissa())); - s.add32(value_.exponent()); - } - else - { - constexpr std::int64_t min = 100'000'000'000'000'000LL; - constexpr std::int64_t max = min * 10 - 1; - static_assert( - min < (std::numeric_limits::max() - 1 / 10)); - auto const [mantissa, exponent] = value_.normalizeToRange(min, max); - s.add64(mantissa); - s.add32(exponent); - } + + auto const mantissa = value_.mantissa(); + auto const exponent = value_.exponent(); + XRPL_ASSERT_PARTS( + mantissa <= std::numeric_limits::max() && + mantissa >= std::numeric_limits::min(), + "ripple::STNumber::add", + "mantissa in valid range"); + s.add64(mantissa); + s.add32(exponent); } Number const& @@ -202,7 +191,7 @@ numberFromJson(SField const& field, Json::Value const& value) // Number mantissas are much bigger than the allowable parsed values, so // it can't be out of range. static_assert( - std::numeric_limits::max() > + std::numeric_limits::max() >= std::numeric_limits::max()); } else @@ -214,7 +203,7 @@ numberFromJson(SField const& field, Json::Value const& value) if (parts.negative) mantissa = -mantissa; - return STNumber{field, Number{mantissa, parts.exponent}}; + return STNumber{field, Number{mantissa, parts.exponent, Number::normalized{}}}; } } // namespace ripple diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index ed5d640f59..21b75e09c4 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -34,43 +34,52 @@ public: auto const scale = Number::getMantissaScale(); testcase << "test_limits " << to_string(scale); bool caught = false; - auto const minMantissa = Number::minMantissa(); + auto const minMantissa = Number::minMantissa(); try { - Number x{minMantissa * 10, 32768}; + Number x = Number{minMantissa * 10, 32768, Number::normalized{}}; } catch (std::overflow_error const&) { caught = true; } BEAST_EXPECT(caught); - Number x{minMantissa * 10, 32767}; - BEAST_EXPECT((x == Number{minMantissa, 32768})); - Number z{minMantissa, -32769}; - BEAST_EXPECT(z == Number{}); - Number y{minMantissa * 1'000 + 1'500, 32000}; - BEAST_EXPECT((y == Number{minMantissa + 2, 32003})); - Number m{std::numeric_limits::min()}; + + auto test = [this](auto const& x, auto const& y) { + auto const result = x == y; + std::stringstream ss; + ss << x << " == " << y << " -> " << (result ? "true" : "false"); + BEAST_EXPECTS(result, ss.str()); + }; + + test( + Number{minMantissa * 10, 32767, Number::normalized{}}, + Number{minMantissa, 32768, Number::normalized{}}); + test(Number{minMantissa, -32769, Number::normalized{}}, Number{}); + test( + Number{minMantissa * 1'000 + 1'500, 32000, Number::normalized{}}, + Number{minMantissa + 2, 32003, Number::normalized{}}); // 9,223,372,036,854,775,808 - BEAST_EXPECT( - (m == - Number{ - scale == MantissaRange::small - ? -9'223'372'036'854'776 - : std::numeric_limits::min(), - 18 - Number::mantissaLog()})); - Number M{std::numeric_limits::max()}; - BEAST_EXPECT( - (M == - Number{ - scale == MantissaRange::small - ? 9'223'372'036'854'776 - : std::numeric_limits::max(), - 18 - Number::mantissaLog()})); + + test( + Number{std::numeric_limits::min()}, + Number{ + scale == MantissaRange::small + ? -9'223'372'036'854'776 + : std::numeric_limits::min(), + 18 - Number::mantissaLog()}); + test( + Number{std::numeric_limits::max()}, + Number{ + scale == MantissaRange::small + ? 9'223'372'036'854'776 + : std::numeric_limits::max(), + 18 - Number::mantissaLog()}); caught = false; try { - Number q{minMantissa * 100 - 1, 32767}; + Number q = + Number{minMantissa * 100 - 1, 32767, Number::normalized{}}; } catch (std::overflow_error const&) { @@ -79,92 +88,6 @@ public: BEAST_EXPECT(caught); } - void - testToString() - { - auto const scale = Number::getMantissaScale(); - testcase << "testToString " << to_string(scale); - - auto test = [this](Number const& n, std::string const& expected) { - auto const result = to_string(n); - std::stringstream ss; - ss << "to_string(" << result << "). Expected: " << expected; - BEAST_EXPECTS(result == expected, ss.str()); - }; - - test(Number(-2, 0), "-2"); - test(Number(0, 0), "0"); - test(Number(2, 0), "2"); - test(Number(25, -3), "0.025"); - test(Number(-25, -3), "-0.025"); - test(Number(25, 1), "250"); - test(Number(-25, 1), "-250"); - switch (scale) - { - case MantissaRange::small: - test(Number(2, 20), "2000000000000000e5"); - test(Number(-2, -20), "-2000000000000000e-35"); - // Test the edges - // ((exponent < -(25)) || (exponent > -(5))))) - test(Number(2, -10), "0.0000000002"); - test(Number(2, -11), "2000000000000000e-26"); - - test(Number(-2, 10), "-20000000000"); - test(Number(-2, 11), "-2000000000000000e-4"); - - test(Number::min(), "1000000000000000e-32768"); - test(Number::max(), "9999999999999999e32768"); - test(Number::lowest(), "-9999999999999999e32768"); - { - NumberRoundModeGuard mg(Number::towards_zero); - - test( - Number{ - numberuint(9'999'999'999'999'999) * 1000 + 999, - -3}, - "9999999999999999"); - test( - -(Number{ - numberuint(9'999'999'999'999'999) * 1000 + 999, - -3}), - "-9999999999999999"); - } - break; - case MantissaRange::large: - test(Number(2, 20), "2000000000000000000e2"); - test(Number(-2, -20), "-2000000000000000000e-38"); - // Test the edges - // ((exponent < -(28)) || (exponent > -(8))))) - test(Number(2, -10), "0.0000000002"); - test(Number(2, -11), "2000000000000000000e-29"); - - test(Number(-2, 10), "-20000000000"); - test(Number(-2, 11), "-2000000000000000000e-7"); - - test(Number::min(), "1000000000000000000e-32768"); - test(Number::max(), "9999999999999999999e32768"); - test(Number::lowest(), "-9999999999999999999e32768"); - { - NumberRoundModeGuard mg(Number::towards_zero); - - test( - Number{ - numberuint(9'999'999'999'999'999) * 1000 + 999, - 0}, - "9999999999999999990"); - test( - -(Number{ - numberuint(9'999'999'999'999'999) * 1000 + 999, - 0}), - "-9999999999999999990"); - } - break; - break; - default: - BEAST_EXPECT(false); - } - } - void test_add() { @@ -206,10 +129,15 @@ public: {Number{-1'000'000'000'000'000, -15}, Number{6'555'555'555'555'555, -29}, Number{ - -(numberint(9'999'999'999'999'344) * 1'000 + 444), -19}}, + -(numberint(9'999'999'999'999'344) * 1'000 + 444), + -19, + Number::normalized{}}}, {Number{-6'555'555'555'555'555, -29}, Number{1'000'000'000'000'000, -15}, - Number{numberint(9'999'999'999'999'344) * 1'000 + 444, -19}}, + Number{ + numberint(9'999'999'999'999'344) * 1'000 + 444, + -19, + Number::normalized{}}}, {Number{}, Number{5}, Number{5}}, {Number{5}, Number{}, Number{5}}, {Number{5'555'555'555'555'555'000, -32768}, @@ -228,17 +156,28 @@ public: {Number{-1'000'000'000'000'000'000, -18}, Number{6'555'555'555'555'555'555, -35}, Number{ - -(numberint(9'999'999'999'999'999) * 1'000 + 344), -19}}, + -(numberint(9'999'999'999'999'999) * 1'000 + 344), + -19, + Number::normalized{}}}, {Number{-6'555'555'555'555'555'555, -35}, Number{1'000'000'000'000'000'000, -18}, - Number{numberint(9'999'999'999'999'999) * 1'000 + 344, -19}}, + Number{ + numberint(9'999'999'999'999'999) * 1'000 + 344, + -19, + Number::normalized{}}}, {Number{}, Number{5}, Number{5}}, {Number{5'555'555'555'555'555'555, -32768}, Number{-5'555'555'555'555'555'554, -32768}, Number{0}}, - {Number{-(numberint(9'999'999'999'999'999) * 1'000 + 999), -37}, + {Number{ + -(numberint(9'999'999'999'999'999) * 1'000 + 999), + -37, + Number::normalized{}}, Number{1'000'000'000'000'000'000, -18}, - Number{numberint(9'999'999'999'999'999) * 1'000 + 990, -19}}}); + Number{ + numberint(9'999'999'999'999'999) * 1'000 + 990, + -19, + Number::normalized{}}}}); auto test = [this](auto const& c) { for (auto const& [x, y, z] : c) { @@ -256,27 +195,8 @@ public: bool caught = false; try { - if (scale == MantissaRange::small) - Number{9'999'999'999'999'999, 32768} + - Number{5'000'000'000'000'000, 32767}; - else - Number{numberint(9'999'999'999'999'999) * 1'000, 32768} + - Number{5'000'000'000'000'000'000, 32767}; - } - catch (std::overflow_error const&) - { - caught = true; - } - BEAST_EXPECT(caught); - } - if (scale != MantissaRange::small) - { - bool caught = false; - try - { - Number{ - numberuint(9'999'999'999'999'999) * 1000 + 999, 32768} + - Number{5'000'000'000'000'000'000, 32767}; + Number{Number::maxMantissa(), 32768, Number::normalized{}} + + Number{Number::minMantissa() * 5, 32767}; } catch (std::overflow_error const&) { @@ -315,11 +235,16 @@ public: // with larger mantissa {{Number{1'000'000'000'000'000, -15}, Number{6'555'555'555'555'555, -29}, - Number{numberint(9'999'999'999'999'344) * 1'000 + 444, -19}}, + Number{ + numberint(9'999'999'999'999'344) * 1'000 + 444, + -19, + Number::normalized{}}}, {Number{6'555'555'555'555'555, -29}, Number{1'000'000'000'000'000, -15}, Number{ - -(numberint(9'999'999'999'999'344) * 1'000 + 444), -19}}, + -(numberint(9'999'999'999'999'344) * 1'000 + 444), + -19, + Number::normalized{}}}, {Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'000, -15}, Number{0}}, @@ -332,11 +257,16 @@ public: // Items from cSmall expanded for the larger mantissa {Number{1'000'000'000'000'000'000, -18}, Number{6'555'555'555'555'555'555, -32}, - Number{numberint(9'999'999'999'999'344) * 1'000 + 444, -19}}, + Number{ + numberint(9'999'999'999'999'344) * 1'000 + 444, + -19, + Number::normalized{}}}, {Number{6'555'555'555'555'555'555, -32}, Number{1'000'000'000'000'000'000, -18}, Number{ - -(numberint(9'999'999'999'999'344) * 1'000 + 444), -19}}, + -(numberint(9'999'999'999'999'344) * 1'000 + 444), + -19, + Number::normalized{}}}, {Number{1'000'000'000'000'000'000, -18}, Number{1'000'000'000'000'000'000, -18}, Number{0}}, @@ -383,6 +313,9 @@ public: else test(cLarge); }; + auto const maxMantissa = Number::maxMantissa(); + auto const maxInt64 = std::numeric_limits::max(); + saveNumberRoundMode save{Number::setround(Number::to_nearest)}; { auto const cSmall = std::to_array({ @@ -423,7 +356,10 @@ public: Number{1999999999999999862, -18}}, {Number{3214285714285706, -15}, Number{3111111111111119, -15}, - Number{numberint(9'999'999'999'999'999) * 1000 + 579, -18}}, + Number{ + numberint(9'999'999'999'999'999) * 1000 + 579, + -18, + Number::normalized{}}}, {Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}}, @@ -442,10 +378,14 @@ public: {Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}}, - // Maximum mantissa range - {Number{numberint(9'999'999'999'999'999) * 1000 + 999, 0}, - Number{numberint(9'999'999'999'999'999) * 1000 + 999, 0}, - Number{numberint(9'999'999'999'999'999) * 1000 + 998, 19}}, + // Maximum mantissa range - rounds up to 1e19 + {Number{maxMantissa, 0, Number::normalized{}}, + Number{maxMantissa, 0, Number::normalized{}}, + Number{1, 38}}, + // Maximum int64 range + {Number{maxInt64, 0}, + Number{maxInt64, 0}, + Number{85'070'591'730'234'615'85, 19}}, }); tests(cSmall, cLarge); } @@ -474,37 +414,52 @@ public: // Note that items with extremely large mantissas need to be // calculated, because otherwise they overflow uint64. Items // from C with larger mantissa - {{Number{7}, Number{8}, Number{56}}, - {Number{1414213562373095, -15}, - Number{1414213562373095, -15}, - Number{1999999999999999861, -18}}, - {Number{-1414213562373095, -15}, - Number{1414213562373095, -15}, - Number{-1999999999999999861, -18}}, - {Number{-1414213562373095, -15}, - Number{-1414213562373095, -15}, - Number{1999999999999999861, -18}}, - {Number{3214285714285706, -15}, - Number{3111111111111119, -15}, - Number{numberint(9999999999999999) * 1000 + 579, -18}}, - {Number{1000000000000000000, -32768}, - Number{1000000000000000000, -32768}, - Number{0}}, - // Items from cSmall expanded for the larger mantissa, except - // duplicates. - // Sadly, it looks like sqrt(2)^2 != 2 with higher precision - {Number{1414213562373095049, -18}, - Number{1414213562373095049, -18}, - Number{2, 0}}, - {Number{-1414213562373095048, -18}, - Number{1414213562373095048, -18}, - Number{-1999999999999999997, -18}}, - {Number{-1414213562373095048, -18}, - Number{-1414213562373095049, -18}, - Number{1999999999999999999, -18}}, - {Number{3214285714285714278, -18}, - Number{3111111111111111119, -18}, - Number{10, 0}}}); + { + {Number{7}, Number{8}, Number{56}}, + {Number{1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{1999999999999999861, -18}}, + {Number{-1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{-1999999999999999861, -18}}, + {Number{-1414213562373095, -15}, + Number{-1414213562373095, -15}, + Number{1999999999999999861, -18}}, + {Number{3214285714285706, -15}, + Number{3111111111111119, -15}, + Number{ + numberint(9999999999999999) * 1000 + 579, + -18, + Number::normalized{}}}, + {Number{1000000000000000000, -32768}, + Number{1000000000000000000, -32768}, + Number{0}}, + // Items from cSmall expanded for the larger mantissa, + // except duplicates. Sadly, it looks like sqrt(2)^2 != 2 + // with higher precision + {Number{1414213562373095049, -18}, + Number{1414213562373095049, -18}, + Number{2, 0}}, + {Number{-1414213562373095048, -18}, + Number{1414213562373095048, -18}, + Number{-1999999999999999997, -18}}, + {Number{-1414213562373095048, -18}, + Number{-1414213562373095049, -18}, + Number{1999999999999999999, -18}}, + {Number{3214285714285714278, -18}, + Number{3111111111111111119, -18}, + Number{10, 0}}, + // Maximum mantissa range - rounds down to maxMantissa/10e1 + // 99'999'999'999'999'999'800'000'000'000'000'000'100 + {Number{maxMantissa, 0, Number::normalized{}}, + Number{maxMantissa, 0, Number::normalized{}}, + Number{maxMantissa / 10 - 1, 20, Number::normalized{}}}, + // Maximum int64 range + // 85'070'591'730'234'615'847'396'907'784'232'501'249 + {Number{maxInt64, 0}, + Number{maxInt64, 0}, + Number{85'070'591'730'234'615'84, 19}}, + }); tests(cSmall, cLarge); } Number::setround(Number::downward); @@ -532,38 +487,52 @@ public: // Note that items with extremely large mantissas need to be // calculated, because otherwise they overflow uint64. Items // from C with larger mantissa - {{Number{7}, Number{8}, Number{56}}, - {Number{1414213562373095, -15}, - Number{1414213562373095, -15}, - Number{1999999999999999861, -18}}, - {Number{-1414213562373095, -15}, - Number{1414213562373095, -15}, - Number{-1999999999999999862, -18}}, - {Number{-1414213562373095, -15}, - Number{-1414213562373095, -15}, - Number{1999999999999999861, -18}}, - {Number{3214285714285706, -15}, - Number{3111111111111119, -15}, - Number{ - numberint(9'999'999'999'999'999) * 1000 + 579, -18}}, - {Number{1000000000000000000, -32768}, - Number{1000000000000000000, -32768}, - Number{0}}, - // Items from cSmall expanded for the larger mantissa, except - // duplicates. - // Sadly, it looks like sqrt(2)^2 != 2 with higher precision - {Number{1414213562373095049, -18}, - Number{1414213562373095049, -18}, - Number{2, 0}}, - {Number{-1414213562373095048, -18}, - Number{1414213562373095048, -18}, - Number{-1999999999999999998, -18}}, - {Number{-1414213562373095048, -18}, - Number{-1414213562373095049, -18}, - Number{1999999999999999999, -18}}, - {Number{3214285714285714278, -18}, - Number{3111111111111111119, -18}, - Number{10, 0}}}); + { + {Number{7}, Number{8}, Number{56}}, + {Number{1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{1999999999999999861, -18}}, + {Number{-1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{-1999999999999999862, -18}}, + {Number{-1414213562373095, -15}, + Number{-1414213562373095, -15}, + Number{1999999999999999861, -18}}, + {Number{3214285714285706, -15}, + Number{3111111111111119, -15}, + Number{ + numberint(9'999'999'999'999'999) * 1000 + 579, + -18, + Number::normalized{}}}, + {Number{1000000000000000000, -32768}, + Number{1000000000000000000, -32768}, + Number{0}}, + // Items from cSmall expanded for the larger mantissa, + // except duplicates. Sadly, it looks like sqrt(2)^2 != 2 + // with higher precision + {Number{1414213562373095049, -18}, + Number{1414213562373095049, -18}, + Number{2, 0}}, + {Number{-1414213562373095048, -18}, + Number{1414213562373095048, -18}, + Number{-1999999999999999998, -18}}, + {Number{-1414213562373095048, -18}, + Number{-1414213562373095049, -18}, + Number{1999999999999999999, -18}}, + {Number{3214285714285714278, -18}, + Number{3111111111111111119, -18}, + Number{10, 0}}, + // Maximum mantissa range - rounds down to maxMantissa/10e1 + // 99'999'999'999'999'999'800'000'000'000'000'000'100 + {Number{maxMantissa, 0, Number::normalized{}}, + Number{maxMantissa, 0, Number::normalized{}}, + Number{maxMantissa / 10 - 1, 20, Number::normalized{}}}, + // Maximum int64 range + // 85'070'591'730'234'615'847'396'907'784'232'501'249 + {Number{maxInt64, 0}, + Number{maxInt64, 0}, + Number{85'070'591'730'234'615'84, 19}}, + }); tests(cSmall, cLarge); } Number::setround(Number::upward); @@ -591,37 +560,49 @@ public: // Note that items with extremely large mantissas need to be // calculated, because otherwise they overflow uint64. Items // from C with larger mantissa - {{Number{7}, Number{8}, Number{56}}, - {Number{1414213562373095, -15}, - Number{1414213562373095, -15}, - Number{1999999999999999862, -18}}, - {Number{-1414213562373095, -15}, - Number{1414213562373095, -15}, - Number{-1999999999999999861, -18}}, - {Number{-1414213562373095, -15}, - Number{-1414213562373095, -15}, - Number{1999999999999999862, -18}}, - {Number{3214285714285706, -15}, - Number{3111111111111119, -15}, - Number{999999999999999958, -17}}, - {Number{1000000000000000000, -32768}, - Number{1000000000000000000, -32768}, - Number{0}}, - // Items from cSmall expanded for the larger mantissa, - // except duplicates. Sadly, it looks like sqrt(2)^2 != 2 - // with higher precision - {Number{1414213562373095049, -18}, - Number{1414213562373095049, -18}, - Number{2000000000000000001, -18}}, - {Number{-1414213562373095048, -18}, - Number{1414213562373095048, -18}, - Number{-1999999999999999997, -18}}, - {Number{-1414213562373095048, -18}, - Number{-1414213562373095049, -18}, - Number{2, 0}}, - {Number{3214285714285714278, -18}, - Number{3111111111111111119, -18}, - Number{1000000000000000001, -17}}}); + { + {Number{7}, Number{8}, Number{56}}, + {Number{1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{1999999999999999862, -18}}, + {Number{-1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{-1999999999999999861, -18}}, + {Number{-1414213562373095, -15}, + Number{-1414213562373095, -15}, + Number{1999999999999999862, -18}}, + {Number{3214285714285706, -15}, + Number{3111111111111119, -15}, + Number{999999999999999958, -17}}, + {Number{1000000000000000000, -32768}, + Number{1000000000000000000, -32768}, + Number{0}}, + // Items from cSmall expanded for the larger mantissa, + // except duplicates. Sadly, it looks like sqrt(2)^2 != 2 + // with higher precision + {Number{1414213562373095049, -18}, + Number{1414213562373095049, -18}, + Number{2000000000000000001, -18}}, + {Number{-1414213562373095048, -18}, + Number{1414213562373095048, -18}, + Number{-1999999999999999997, -18}}, + {Number{-1414213562373095048, -18}, + Number{-1414213562373095049, -18}, + Number{2, 0}}, + {Number{3214285714285714278, -18}, + Number{3111111111111111119, -18}, + Number{1000000000000000001, -17}}, + // Maximum mantissa range - rounds up to minMantissa*10 + // 1e19*1e19=1e38 + {Number{maxMantissa, 0, Number::normalized{}}, + Number{maxMantissa, 0, Number::normalized{}}, + Number{1, 38}}, + // Maximum int64 range + // 85'070'591'730'234'615'847'396'907'784'232'501'249 + {Number{maxInt64, 0}, + Number{maxInt64, 0}, + Number{85'070'591'730'234'615'85, 19}}, + }); tests(cSmall, cLarge); } testcase << "test_mul " << to_string(Number::getMantissaScale()) @@ -630,27 +611,8 @@ public: bool caught = false; try { - if (scale == MantissaRange::small) - Number{9'999'999'999'999'999, 32768} * - Number{5'000'000'000'000'000, 32767}; - else - Number{numberint(9'999'999'999'999'999) * 1'000, 32768} * - Number{5'000'000'000'000'000'000, 32767}; - } - catch (std::overflow_error const&) - { - caught = true; - } - BEAST_EXPECT(caught); - } - if (scale != MantissaRange::small) - { - bool caught = false; - try - { - Number{ - numberint(9'999'999'999'999'999) * 1000 + 999, 32768} + - Number{5'000'000'000'000'000'000, 32767}; + Number{maxMantissa, 32768, Number::normalized{}} * + Number{Number::minMantissa() * 5, 32767}; } catch (std::overflow_error const&) { @@ -676,6 +638,7 @@ public: BEAST_EXPECTS(result == z, ss.str()); } }; + auto const maxMantissa = Number::maxMantissa(); auto tests = [&](auto const& cSmall, auto const& cLarge) { if (scale == MantissaRange::small) test(cSmall); @@ -722,11 +685,9 @@ public: {Number{1414213562373095049, -13}, Number{1414213562373095049, -13}, Number{1}}, - {Number{numberint(9'999'999'999'999'999) * 1'000 + 999, 0}, + {Number{maxMantissa, 0, Number::normalized{}}, Number{1'000'000'000'000'000'000}, - Number{ - numberint(9'999'999'999'999'999) * 1'000 + 999, - -18}}}); + Number{maxMantissa, -18, Number::normalized{}}}}); tests(cSmall, cLarge); } testcase << "test_div " << to_string(Number::getMantissaScale()) @@ -771,11 +732,9 @@ public: {Number{1414213562373095049, -13}, Number{1414213562373095049, -13}, Number{1}}, - {Number{numberint(9'999'999'999'999'999) * 1'000 + 999, 0}, + {Number{maxMantissa, 0, Number::normalized{}}, Number{1'000'000'000'000'000'000}, - Number{ - numberint(9'999'999'999'999'999) * 1'000 + 999, - -18}}}); + Number{maxMantissa, -18, Number::normalized{}}}}); tests(cSmall, cLarge); } testcase << "test_div " << to_string(Number::getMantissaScale()) @@ -820,11 +779,9 @@ public: {Number{1414213562373095049, -13}, Number{1414213562373095049, -13}, Number{1}}, - {Number{numberint(9'999'999'999'999'999) * 1'000 + 999, 0}, + {Number{maxMantissa, 0, Number::normalized{}}, Number{1'000'000'000'000'000'000}, - Number{ - numberint(9'999'999'999'999'999) * 1'000 + 999, - -18}}}); + Number{maxMantissa, -18, Number::normalized{}}}}); tests(cSmall, cLarge); } testcase << "test_div " << to_string(Number::getMantissaScale()) @@ -869,11 +826,9 @@ public: {Number{1414213562373095049, -13}, Number{1414213562373095049, -13}, Number{1}}, - {Number{numberint(9'999'999'999'999'999) * 1'000 + 999, 0}, + {Number{maxMantissa, 0, Number::normalized{}}, Number{1'000'000'000'000'000'000}, - Number{ - numberint(9'999'999'999'999'999) * 1'000 + 999, - -18}}}); + Number{maxMantissa, -18, Number::normalized{}}}}); tests(cSmall, cLarge); } testcase << "test_div " << to_string(Number::getMantissaScale()) @@ -1002,10 +957,16 @@ public: {Number{-64}, 3, Number{-262144}}, {Number{64}, 11, - Number{numberint(73786976294838206) * 1000 + 464, 0}}, + Number{ + numberint(73786976294838206) * 1000 + 464, + 0, + Number::normalized{}}}, {Number{-64}, 11, - Number{-(numberint(73786976294838206) * 1000 + 464), 0}}}; + -(Number{ + numberint(73786976294838206) * 1000 + 464, + 0, + Number::normalized{}})}}; for (auto const& [x, y, z] : c) BEAST_EXPECT((power(x, y) == z)); } @@ -1263,6 +1224,105 @@ public: BEAST_EXPECT((squelch(Number{-9, -7}, limit) == Number{0})); } + void + testToString() + { + auto const scale = Number::getMantissaScale(); + testcase << "testToString " << to_string(scale); + + auto test = [this](Number const& n, std::string const& expected) { + auto const result = to_string(n); + std::stringstream ss; + ss << "to_string(" << result << "). Expected: " << expected; + BEAST_EXPECTS(result == expected, ss.str()); + }; + + test(Number(-2, 0), "-2"); + test(Number(0, 0), "0"); + test(Number(2, 0), "2"); + test(Number(25, -3), "0.025"); + test(Number(-25, -3), "-0.025"); + test(Number(25, 1), "250"); + test(Number(-25, 1), "-250"); + switch (scale) + { + case MantissaRange::small: + test(Number(2, 20), "2000000000000000e5"); + test(Number(-2, -20), "-2000000000000000e-35"); + // Test the edges + // ((exponent < -(25)) || (exponent > -(5))))) + test(Number(2, -10), "0.0000000002"); + test(Number(2, -11), "2000000000000000e-26"); + + test(Number(-2, 10), "-20000000000"); + test(Number(-2, 11), "-2000000000000000e-4"); + + test(Number::min(), "1000000000000000e-32768"); + test(Number::max(), "9999999999999999e32768"); + test(Number::lowest(), "-9999999999999999e32768"); + { + NumberRoundModeGuard mg(Number::towards_zero); + + auto const maxMantissa = Number::maxMantissa(); + BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999); + test( + Number{ + maxMantissa * 1000 + 999, -3, Number::normalized()}, + "9999999999999999"); + test( + -(Number{ + maxMantissa * 1000 + 999, + -3, + Number::normalized()}), + "-9999999999999999"); + + test( + Number{std::numeric_limits::max(), -3}, + "9223372036854775"); + test( + -(Number{std::numeric_limits::max(), -3}), + "-9223372036854775"); + } + break; + case MantissaRange::large: + test(Number(2, 20), "2000000000000000000e2"); + test(Number(-2, -20), "-2000000000000000000e-38"); + // Test the edges + // ((exponent < -(28)) || (exponent > -(8))))) + test(Number(2, -10), "0.0000000002"); + test(Number(2, -11), "2000000000000000000e-29"); + + test(Number(-2, 10), "-20000000000"); + test(Number(-2, 11), "-2000000000000000000e-7"); + + test(Number::min(), "1000000000000000000e-32768"); + test(Number::max(), "9999999999999999999e32768"); + test(Number::lowest(), "-9999999999999999999e32768"); + { + NumberRoundModeGuard mg(Number::towards_zero); + + auto const maxMantissa = Number::maxMantissa(); + BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999); + test( + Number{maxMantissa, 0, Number::normalized{}}, + "9999999999999999990"); + test( + -(Number{maxMantissa, 0, Number::normalized{}}), + "-9999999999999999990"); + + test( + Number{std::numeric_limits::max(), 0}, + "9223372036854775807"); + test( + -(Number{std::numeric_limits::max(), 0}), + "-9223372036854775807"); + } + break; + default: + BEAST_EXPECT(false); + } + } + void test_relationals() { @@ -1369,7 +1429,8 @@ public: BEAST_EXPECT( (power(maxInt64, 2) == Number{85'070'591'730'234'62, 22})); - Number const max = Number{Number::maxMantissa(), 0}; + Number const max = + Number{Number::maxMantissa(), 0, Number::normalized{}}; BEAST_EXPECT(max.exponent() <= 0); // 99'999'999'999'999'980'000'000'000'000'001 - 32 digits BEAST_EXPECT((power(max, 2) == Number{99'999'999'999'999'98, 16})); @@ -1388,12 +1449,18 @@ public: BEAST_EXPECT( (power(maxInt64, 2) == Number{85'070'591'730'234'615'85, 19})); - Number const max = Number{Number::maxMantissa(), 0}; - BEAST_EXPECT(max.exponent() <= 0); - // 99999999999999999980000000000000000001 - also 38 digits - BEAST_EXPECT( - (power(max, 2) == - Number{numberint(9'999'999'999'999'999) * 1000 + 998, 19})); + NumberRoundModeGuard mg(Number::towards_zero); + + auto const maxMantissa = Number::maxMantissa(); + Number const max = Number{maxMantissa, 0, Number::normalized{}}; + BEAST_EXPECT(max.mantissa() == maxMantissa / 10); + BEAST_EXPECT(max.exponent() == 1); + // 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38 + // digits + BEAST_EXPECT(( + power(max, 2) == + Number{ + maxMantissa / 10 - 1, 20, Number::normalized{}})); } } diff --git a/src/test/protocol/STNumber_test.cpp b/src/test/protocol/STNumber_test.cpp index eb9e53f8df..1192b2c4a4 100644 --- a/src/test/protocol/STNumber_test.cpp +++ b/src/test/protocol/STNumber_test.cpp @@ -153,9 +153,9 @@ struct STNumber_test : public beast::unit_test::suite STNumber( sfNumber, -Number{ - numberint(9'223'372'036'854'775) * 1000 + - 808, - 0})); + numberint(9'223'372'036'854'775) * 1000 + 808, + 0, + Number::normalized{}})); } }