From d27788f12a91d7efe2fd870ed05de980666ec537 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Fri, 23 Jan 2026 15:51:38 -0500 Subject: [PATCH] Use 2^63-1 as maxMantissa for large range - That makes minMantissa 2^63/10+1. - Simplifies many of the existing operations, and removes the need for the accessors (mantissa() & exponent()) to do any math. --- include/xrpl/basics/Number.h | 177 +++++++--- include/xrpl/protocol/Protocol.h | 2 +- include/xrpl/protocol/SystemParameters.h | 2 +- src/libxrpl/basics/Number.cpp | 88 +++-- src/test/basics/Number_test.cpp | 421 +++++++++++++++-------- 5 files changed, 451 insertions(+), 239 deletions(-) diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index 2f467fb036..7dc6889457 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -17,18 +17,37 @@ class Number; std::string to_string(Number const& amount); +/** Returns a rough estimate of log10(value). + * + * The return value is a pair (log, rem), where log is the estimated log10, + * and rem is value divided by 10^log. If rem is 1, then value is an exact + * power of ten, and log is the exact log10(value). + * + * This function only works for positive values. + */ +template +constexpr std::pair +logTenEstimate(T value) +{ + int log = 0; + T remainder = value; + while (value >= 10) + { + if (value % 10 == 0) + remainder = remainder / 10; + value /= 10; + ++log; + } + return {log, remainder}; +} + template constexpr std::optional logTen(T value) { - int log = 0; - while (value >= 10 && value % 10 == 0) - { - value /= 10; - ++log; - } - if (value == 1) - return log; + auto const est = logTenEstimate(value); + if (est.second == 1) + return est.first; return std::nullopt; } @@ -42,12 +61,10 @@ isPowerOfTen(T value) /** MantissaRange defines a range for the mantissa of a normalized Number. * * The mantissa is in the range [min, max], where - * * min is a power of 10, and - * * max = min * 10 - 1. * * The mantissa_scale enum indicates whether the range is "small" or "large". * This intentionally restricts the number of MantissaRanges that can be - * instantiated to two: one for each scale. + * used to two: one for each scale. * * The "small" scale is based on the behavior of STAmount for IOUs. It has a min * value of 10^15, and a max value of 10^16-1. This was sufficient for @@ -61,8 +78,8 @@ isPowerOfTen(T value) * "large" scale. * * The "large" scale is intended to represent all values that can be represented - * by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max - * value of 10^19-1. + * by an STAmount - IOUs, XRP, and MPTs. It has a min value of 2^63/10+1 + * (truncated), and a max value of 2^63-1. * * Note that if the mentioned amendments are eventually retired, this class * should be left in place, but the "small" scale option should be removed. This @@ -74,28 +91,42 @@ struct MantissaRange enum mantissa_scale { small, large }; explicit constexpr MantissaRange(mantissa_scale scale_) - : min(getMin(scale_)) - , max(min * 10 - 1) - , log(logTen(min).value_or(-1)) + : max(getMax(scale_)) + , min(computeMin(max)) + , referenceMin(getReferenceMin(scale_, min)) + , log(computeLog(min)) , scale(scale_) { + // Since this is constexpr, if any of these throw, it won't compile + if (min * 10 <= max) + throw std::out_of_range("min * 10 <= max"); + if (max / 10 >= min) + throw std::out_of_range("max / 10 >= min"); + if ((min - 1) * 10 > max) + throw std::out_of_range("(min - 1) * 10 > max"); + // This is a little hacky + if ((max + 10) / 10 < min) + throw std::out_of_range("(max + 10) / 10 < min"); } - rep min; rep max; + rep min; + // This is not a great name. Used to determine if mantissas are in range, + // but have fewer digits than max + rep referenceMin; int log; mantissa_scale scale; private: static constexpr rep - getMin(mantissa_scale scale_) + getMax(mantissa_scale scale) { - switch (scale_) + switch (scale) { case small: - return 1'000'000'000'000'000ULL; + return 9'999'999'999'999'999ULL; case large: - return 1'000'000'000'000'000'000ULL; + return std::numeric_limits::max(); default: // Since this can never be called outside a non-constexpr // context, this throw assures that the build fails if an @@ -103,6 +134,38 @@ private: throw std::runtime_error("Unknown mantissa scale"); } } + + static constexpr rep + computeMin(rep max) + { + auto min = max + 1; + auto const r = min % 10; + min /= 10; + if (r != 0) + ++min; + return min; + } + + static constexpr rep + getReferenceMin(mantissa_scale scale, rep min) + { + switch (scale) + { + case large: + return 1'000'000'000'000'000'000ULL; + default: + if (isPowerOfTen(min)) + return min; + throw std::runtime_error("Unknown/bad mantissa scale"); + } + } + + static constexpr rep + computeLog(rep min) + { + auto const estimate = logTenEstimate(min); + return estimate.first + (estimate.second == 1 ? 0 : 1); + } }; // Like std::integral, but only 64-bit integral types. @@ -137,9 +200,7 @@ concept Integral64 = * 1. Normalization can be disabled by using the "unchecked" ctor tag. This * should only be used at specific conversion points, some constexpr * values, and in unit tests. - * 2. The max of the "large" range, 10^19-1, is the largest 10^X-1 value that - * fits in an unsigned 64-bit number. (10^19-1 < 2^64-1 and - * 10^20-1 > 2^64-1). This avoids under- and overflows. + * 2. The max of the "large" range, 2^63-1, TODO: explain the large range. * * ---- External Interface ---- * @@ -153,7 +214,7 @@ concept Integral64 = * * Note: * 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large" - * mantissa range. + * mantissa range. TODO: update this explanation. * 2. The functions mantissa() and exponent() return the external view of the * Number value, specifically using a signed 63-bit mantissa. This may * require altering the internal representation to fit into that range @@ -213,6 +274,7 @@ class Number using rep = std::int64_t; using internalrep = MantissaRange::rep; + // TODO: Get rid of negative_ and convert mantissa back to rep bool negative_{false}; internalrep mantissa_{0}; int exponent_{std::numeric_limits::lowest()}; @@ -222,9 +284,11 @@ public: constexpr static int minExponent = -32768; constexpr static int maxExponent = 32768; +#if MAXREP constexpr static internalrep maxRep = std::numeric_limits::max(); static_assert(maxRep == 9'223'372'036'854'775'807); static_assert(-maxRep == std::numeric_limits::min() + 1); +#endif // May need to make unchecked private struct unchecked @@ -457,16 +521,31 @@ private: static_assert(isPowerOfTen(smallRange.min)); static_assert(smallRange.min == 1'000'000'000'000'000LL); static_assert(smallRange.max == 9'999'999'999'999'999LL); + static_assert(smallRange.referenceMin == smallRange.min); static_assert(smallRange.log == 15); +#if MAXREP static_assert(smallRange.min < maxRep); static_assert(smallRange.max < maxRep); +#endif constexpr static MantissaRange largeRange{MantissaRange::large}; - static_assert(isPowerOfTen(largeRange.min)); - static_assert(largeRange.min == 1'000'000'000'000'000'000ULL); - static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL)); + static_assert(!isPowerOfTen(largeRange.min)); + static_assert(largeRange.min == 922'337'203'685'477'581ULL); + static_assert(largeRange.max == internalrep(9'223'372'036'854'775'807ULL)); + static_assert(largeRange.max == std::numeric_limits::max()); + static_assert(largeRange.referenceMin == 1'000'000'000'000'000'000ULL); static_assert(largeRange.log == 18); + // There are 2 values that will not fit in largeRange without some extra + // work + // * 9223372036854775808 + // * 9223372036854775809 + // They both end up < min, but with a leftover. If they round up, everything + // will be fine. If they don't, well need to bring them up into range. + // Guard::bringIntoRange handles this situation. + +#if MAXREP static_assert(largeRange.min < maxRep); static_assert(largeRange.max > maxRep); +#endif // The range for the mantissa when normalized. // Use reference_wrapper to avoid making copies, and prevent accidentally @@ -516,7 +595,16 @@ private: static internalrep externalToInternal(rep mantissa); + // Safely convert Number to the internal rep where the mantissa always has + // the same number of digits + template + std::tuple + toInternal() const; + class Guard; + +public: + constexpr static internalrep largestMantissa = largeRange.max; }; inline constexpr Number::Number( @@ -570,17 +658,8 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0} inline constexpr Number::rep Number::mantissa() const noexcept { - auto m = mantissa_; - if (m > maxRep) - { - XRPL_ASSERT_PARTS( - !isnormal() || (m % 10 == 0 && m / 10 <= maxRep), - "xrpl::Number::mantissa", - "large normalized mantissa has no remainder"); - m /= 10; - } auto const sign = negative_ ? -1 : 1; - return sign * static_cast(m); + return sign * static_cast(mantissa_); } /** Returns the exponent of the external view of the Number. @@ -591,16 +670,7 @@ Number::mantissa() const noexcept inline constexpr int Number::exponent() const noexcept { - auto e = exponent_; - if (mantissa_ > maxRep) - { - XRPL_ASSERT_PARTS( - !isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= maxRep), - "xrpl::Number::exponent", - "large normalized mantissa has no remainder"); - ++e; - } - return e; + return exponent_; } inline constexpr Number @@ -696,15 +766,13 @@ Number::min() noexcept inline Number Number::max() noexcept { - return Number{ - false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}}; + return Number{false, range_.get().max, maxExponent, unchecked{}}; } inline Number Number::lowest() noexcept { - return Number{ - true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}}; + return Number{true, range_.get().max, maxExponent, unchecked{}}; } inline bool @@ -713,9 +781,8 @@ Number::isnormal() const noexcept MantissaRange const& range = range_; auto const abs_m = mantissa_; return *this == Number{} || - (range.min <= abs_m && abs_m <= range.max && - (abs_m <= maxRep || abs_m % 10 == 0) && minExponent <= exponent_ && - exponent_ <= maxExponent); + (range.min <= abs_m && abs_m <= range.max && // + minExponent <= exponent_ && exponent_ <= maxExponent); } template diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index 43be9d3b45..30065b38bf 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -233,7 +233,7 @@ std::size_t constexpr maxMPTokenMetadataLength = 1024; /** The maximum amount of MPTokenIssuance */ std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull; -static_assert(Number::maxRep >= maxMPTokenAmount); +static_assert(Number::largestMantissa >= maxMPTokenAmount); /** The maximum length of Data payload */ std::size_t constexpr maxDataPayloadLength = 256; diff --git a/include/xrpl/protocol/SystemParameters.h b/include/xrpl/protocol/SystemParameters.h index c2f66e9ea1..fe81ca9bbd 100644 --- a/include/xrpl/protocol/SystemParameters.h +++ b/include/xrpl/protocol/SystemParameters.h @@ -24,7 +24,7 @@ systemName() /** Number of drops in the genesis account. */ constexpr XRPAmount INITIAL_XRP{100'000'000'000 * DROPS_PER_XRP}; static_assert(INITIAL_XRP.drops() == 100'000'000'000'000'000); -static_assert(Number::maxRep >= INITIAL_XRP.drops()); +static_assert(Number::largestMantissa >= INITIAL_XRP.drops()); /** Returns true if the amount does not exceed the initial XRP in existence. */ inline bool diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 436ebf6779..aa88c68210 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -260,7 +260,7 @@ Number::Guard::doRoundUp( ++mantissa; // Ensure mantissa after incrementing fits within both the // min/maxMantissa range and is a valid "rep". - if (mantissa > maxMantissa || mantissa > maxRep) + if (mantissa > maxMantissa) { mantissa /= 10; ++exponent; @@ -299,7 +299,7 @@ Number::Guard::doRound(rep& drops, std::string location) auto r = round(); if (r == 1 || (r == 0 && (drops & 1) == 1)) { - if (drops >= maxRep) + if (drops >= maxMantissa()) { static_assert(sizeof(internalrep) == sizeof(rep)); // This should be impossible, because it's impossible to represent @@ -341,12 +341,36 @@ Number::externalToInternal(rep mantissa) return static_cast(-temp); } +template +std::tuple +Number::toInternal() const +{ + auto exponent = exponent_; + Rep mantissa = mantissa_; + bool const negative = negative_; + + auto const referenceMin = Number::range_.get().referenceMin; + + if (mantissa != 0 && mantissa < referenceMin) + { + // Ensure the mantissa has the correct number of digits + mantissa *= 10; + --exponent; + } + XRPL_ASSERT_PARTS( + mantissa >= referenceMin && mantissa < referenceMin * 10, + "ripple::Number::toInternal()", + "Number is within reference range and has 'log' digits"); + + return {negative, mantissa, exponent}; +} + constexpr Number Number::oneSmall() { return Number{ false, - Number::smallRange.min, + Number::smallRange.referenceMin, -Number::smallRange.log, Number::unchecked{}}; }; @@ -358,7 +382,7 @@ Number::oneLarge() { return Number{ false, - Number::largeRange.min, + Number::largeRange.referenceMin, -Number::largeRange.log, Number::unchecked{}}; }; @@ -387,18 +411,18 @@ doNormalize( { auto constexpr minExponent = Number::minExponent; auto constexpr maxExponent = Number::maxExponent; - auto constexpr maxRep = Number::maxRep; using Guard = Number::Guard; constexpr Number zero = Number{}; - if (mantissa_ == 0) + if (mantissa_ == 0 || (mantissa_ < minMantissa && exponent_ <= minExponent)) { mantissa_ = zero.mantissa_; exponent_ = zero.exponent_; negative = zero.negative_; return; } + auto m = mantissa_; while ((m < minMantissa) && (exponent_ > minExponent)) { @@ -416,7 +440,7 @@ doNormalize( m /= 10; ++exponent_; } - if ((exponent_ < minExponent) || (m < minMantissa)) + if ((exponent_ < minExponent) || (m == 0)) { mantissa_ = zero.mantissa_; exponent_ = zero.exponent_; @@ -424,33 +448,8 @@ doNormalize( return; } - // 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 - // MantissaRange. Cut it down here so that the rounding will be done while - // it's smaller. - // - // Example: 9,900,000,000,000,123,456 > 9,223,372,036,854,775,807, - // so "m" will be modified to 990,000,000,000,012,345. Then that value - // will be rounded to 990,000,000,000,012,345 or - // 990,000,000,000,012,346, 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,123,450 or 9,900,000,000,000,123,460. - // mantissa() will return mantissa_ / 10, and exponent() will return - // exponent_ + 1. - if (m > maxRep) - { - if (exponent_ >= maxExponent) - throw std::overflow_error("Number::normalize 1.5"); - g.push(m % 10); - m /= 10; - ++exponent_; - } - // Before modification, m should be within the min/max range. After - // modification, it must be less than maxRep. In other words, the original - // value should have been no more than maxRep * 10. - // (maxRep * 10 > maxMantissa) XRPL_ASSERT_PARTS( - m <= maxRep, + m <= maxMantissa, "xrpl::doNormalize", "intermediate mantissa fits in int64"); mantissa_ = m; @@ -462,6 +461,7 @@ doNormalize( minMantissa, maxMantissa, "Number::normalize 2"); + XRPL_ASSERT_PARTS( mantissa_ >= minMantissa && mantissa_ <= maxMantissa, "xrpl::doNormalize", @@ -560,13 +560,10 @@ Number::operator+=(Number const& y) // Need to use uint128_t, because large mantissas can overflow when added // together. - bool xn = negative_; - uint128_t xm = mantissa_; - auto xe = exponent_; + auto [xn, xm, xe] = toInternal(); + + auto [yn, ym, ye] = y.toInternal(); - bool yn = y.negative_; - uint128_t ym = y.mantissa_; - auto ye = y.exponent_; Guard g; if (xe < ye) { @@ -598,7 +595,7 @@ Number::operator+=(Number const& y) if (xn == yn) { xm += ym; - if (xm > maxMantissa || xm > maxRep) + if (xm > maxMantissa) { g.push(xm % 10); xm /= 10; @@ -619,7 +616,7 @@ Number::operator+=(Number const& y) xe = ye; xn = yn; } - while (xm < minMantissa && xm * 10 <= maxRep) + while (xm < minMantissa) { xm *= 10; xm -= g.pop(); @@ -701,7 +698,7 @@ Number::operator*=(Number const& y) auto const& minMantissa = range.min; auto const& maxMantissa = range.max; - while (zm > maxMantissa || zm > maxRep) + while (zm > maxMantissa) { // The following is optimization for: // g.push(static_cast(zm % 10)); @@ -843,7 +840,7 @@ Number::operator rep() const } for (; offset > 0; --offset) { - if (drops > maxRep / 10) + if (drops > largeRange.max / 10) throw std::overflow_error("Number::operator rep() overflow"); drops *= 10; } @@ -878,9 +875,8 @@ to_string(Number const& amount) if (amount == zero) return "0"; - auto exponent = amount.exponent_; - auto mantissa = amount.mantissa_; - bool const negative = amount.negative_; + // The mantissa must have a set number of decimal places for this to work + auto [negative, mantissa, exponent] = amount.toInternal(); // Use scientific notation for exponents that are too small or too large auto const rangeLog = Number::mantissaLog(); diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index 1fa5ae6e8f..db169fc1f9 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -32,9 +32,10 @@ public: test_limits() { auto const scale = Number::getMantissaScale(); - testcase << "test_limits " << to_string(scale); - bool caught = false; auto const minMantissa = Number::minMantissa(); + + testcase << "test_limits " << to_string(scale) << ", " << minMantissa; + bool caught = false; try { Number x = @@ -62,8 +63,9 @@ public: Number{}, __LINE__); test( + // Use 1501 to force rounding up Number{false, minMantissa, 32000, Number::normalized{}} * 1'000 + - Number{false, 1'500, 32000, Number::normalized{}}, + Number{false, 1'501, 32000, Number::normalized{}}, Number{false, minMantissa + 2, 32003, Number::normalized{}}, __LINE__); // 9,223,372,036,854,775,808 @@ -198,12 +200,12 @@ public: 9'999'999'999'999'999'990ULL, -19, Number::normalized{}}}, - {Number{Number::maxRep}, + {Number{Number::largestMantissa}, Number{6, -1}, - Number{Number::maxRep / 10, 1}}, - {Number{Number::maxRep - 1}, + Number{Number::largestMantissa / 10, 1}}, + {Number{Number::largestMantissa - 1}, Number{1, 0}, - Number{Number::maxRep}}, + Number{Number::largestMantissa}}, // Test extremes { // Each Number operand rounds up, so the actual mantissa is @@ -221,11 +223,11 @@ public: Number{2, 19}, }, { - // Does not round. Mantissas are going to be > maxRep, so if - // added together as uint64_t's, the result will overflow. - // With addition using uint128_t, there's no problem. After - // normalizing, the resulting mantissa ends up less than - // maxRep. + // Does not round. Mantissas are going to be > + // largestMantissa, so if added together as uint64_t's, the + // result will overflow. With addition using uint128_t, + // there's no problem. After normalizing, the resulting + // mantissa ends up less than largestMantissa. Number{ false, 9'999'999'999'999'999'990ULL, @@ -352,16 +354,24 @@ public: {Number{1'000'000'000'000'000'001, -18}, Number{1'000'000'000'000'000'000, -18}, Number{1'000'000'000'000'000'000, -36}}, - {Number{Number::maxRep}, + {Number{Number::largestMantissa}, Number{6, -1}, - Number{Number::maxRep - 1}}, - {Number{false, Number::maxRep + 1, 0, Number::normalized{}}, + Number{Number::largestMantissa - 1}}, + {Number{ + false, + Number::largestMantissa + 1, + 0, + Number::normalized{}}, Number{1, 0}, - Number{Number::maxRep / 10 + 1, 1}}, - {Number{false, Number::maxRep + 1, 0, Number::normalized{}}, + Number{Number::largestMantissa / 10 + 1, 1}}, + {Number{ + false, + Number::largestMantissa + 1, + 0, + Number::normalized{}}, Number{3, 0}, - Number{Number::maxRep}}, - {power(2, 63), Number{3, 0}, Number{Number::maxRep}}, + Number{Number::largestMantissa}}, + {power(2, 63), Number{3, 0}, Number{Number::largestMantissa}}, }); auto test = [this](auto const& c) { for (auto const& [x, y, z] : c) @@ -384,14 +394,16 @@ public: auto const scale = Number::getMantissaScale(); testcase << "test_mul " << to_string(scale); - using Case = std::tuple; + // Case: Factor 1, Factor 2, Expected product, Line number + using Case = std::tuple; auto test = [this](auto const& c) { - for (auto const& [x, y, z] : c) + for (auto const& [x, y, z, line] : c) { auto const result = x * y; std::stringstream ss; ss << x << " * " << y << " = " << result << ". Expected: " << z; - BEAST_EXPECTS(result == z, ss.str()); + BEAST_EXPECTS( + result == z, ss.str() + " line: " + std::to_string(line)); } }; auto tests = [&](auto const& cSmall, auto const& cLarge) { @@ -401,78 +413,105 @@ public: test(cLarge); }; auto const maxMantissa = Number::maxMantissa(); + auto const maxInternalMantissa = + static_cast( + static_cast(power(10, Number::mantissaLog()))) * + 10 - + 1; saveNumberRoundMode save{Number::setround(Number::to_nearest)}; { auto const cSmall = std::to_array({ - {Number{7}, Number{8}, Number{56}}, + {Number{7}, Number{8}, Number{56}, __LINE__}, {Number{1414213562373095, -15}, Number{1414213562373095, -15}, - Number{2000000000000000, -15}}, + Number{2000000000000000, -15}, + __LINE__}, {Number{-1414213562373095, -15}, Number{1414213562373095, -15}, - Number{-2000000000000000, -15}}, + Number{-2000000000000000, -15}, + __LINE__}, {Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, - Number{2000000000000000, -15}}, + Number{2000000000000000, -15}, + __LINE__}, {Number{3214285714285706, -15}, Number{3111111111111119, -15}, - Number{1000000000000000, -14}}, + Number{1000000000000000, -14}, + __LINE__}, {Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, - Number{0}}, + Number{0}, + __LINE__}, // Maximum mantissa range {Number{9'999'999'999'999'999, 0}, Number{9'999'999'999'999'999, 0}, - Number{9'999'999'999'999'998, 16}}, + Number{9'999'999'999'999'998, 16}, + __LINE__}, }); auto const cLarge = std::to_array({ // 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{7}, Number{8}, Number{56}, __LINE__}, {Number{1414213562373095, -15}, Number{1414213562373095, -15}, - Number{1999999999999999862, -18}}, + Number{1999999999999999862, -18}, + __LINE__}, {Number{-1414213562373095, -15}, Number{1414213562373095, -15}, - Number{-1999999999999999862, -18}}, + Number{-1999999999999999862, -18}, + __LINE__}, {Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, - Number{1999999999999999862, -18}}, + Number{1999999999999999862, -18}, + __LINE__}, {Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{ false, 9'999'999'999'999'999'579ULL, -18, - Number::normalized{}}}, + Number::normalized{}}, + __LINE__}, {Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, - Number{0}}, + Number{0}, + __LINE__}, // 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{2000000000000000001, -18}, + __LINE__}, {Number{-1414213562373095048, -18}, Number{1414213562373095048, -18}, - Number{-1999999999999999998, -18}}, + Number{-1999999999999999998, -18}, + __LINE__}, {Number{-1414213562373095048, -18}, Number{-1414213562373095049, -18}, - Number{1999999999999999999, -18}}, + Number{1999999999999999999, -18}, + __LINE__}, {Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, - Number{10, 0}}, - // Maximum mantissa range - rounds up to 1e19 + Number{10, 0}, + __LINE__}, + // Maximum internal mantissa range - rounds up to 1e19 + {Number{false, maxInternalMantissa, 0, Number::normalized{}}, + Number{false, maxInternalMantissa, 0, Number::normalized{}}, + Number{1, 38}, + __LINE__}, + // Maximum actual mantissa range - same as int64 range {Number{false, maxMantissa, 0, Number::normalized{}}, Number{false, maxMantissa, 0, Number::normalized{}}, - Number{1, 38}}, + Number{85'070'591'730'234'615'85, 19}, + __LINE__}, // Maximum int64 range - {Number{Number::maxRep, 0}, - Number{Number::maxRep, 0}, - Number{85'070'591'730'234'615'85, 19}}, + {Number{Number::largestMantissa, 0}, + Number{Number::largestMantissa, 0}, + Number{85'070'591'730'234'615'85, 19}, + __LINE__}, }); tests(cSmall, cLarge); } @@ -481,76 +520,101 @@ public: << " towards_zero"; { auto const cSmall = std::to_array( - {{Number{7}, Number{8}, Number{56}}, + {{Number{7}, Number{8}, Number{56}, __LINE__}, {Number{1414213562373095, -15}, Number{1414213562373095, -15}, - Number{1999999999999999, -15}}, + Number{1999999999999999, -15}, + __LINE__}, {Number{-1414213562373095, -15}, Number{1414213562373095, -15}, - Number{-1999999999999999, -15}}, + Number{-1999999999999999, -15}, + __LINE__}, {Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, - Number{1999999999999999, -15}}, + Number{1999999999999999, -15}, + __LINE__}, {Number{3214285714285706, -15}, Number{3111111111111119, -15}, - Number{9999999999999999, -15}}, + Number{9999999999999999, -15}, + __LINE__}, {Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, - Number{0}}}); + Number{0}, + __LINE__}}); auto const cLarge = std::to_array( // 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{7}, Number{8}, Number{56}, __LINE__}, {Number{1414213562373095, -15}, Number{1414213562373095, -15}, - Number{1999999999999999861, -18}}, + Number{1999999999999999861, -18}, + __LINE__}, {Number{-1414213562373095, -15}, Number{1414213562373095, -15}, - Number{-1999999999999999861, -18}}, + Number{-1999999999999999861, -18}, + __LINE__}, {Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, - Number{1999999999999999861, -18}}, + Number{1999999999999999861, -18}, + __LINE__}, {Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{ false, 9999999999999999579ULL, -18, - Number::normalized{}}}, + Number::normalized{}}, + __LINE__}, {Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, - Number{0}}, + Number{0}, + __LINE__}, // 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{2, 0}, + __LINE__}, {Number{-1414213562373095048, -18}, Number{1414213562373095048, -18}, - Number{-1999999999999999997, -18}}, + Number{-1999999999999999997, -18}, + __LINE__}, {Number{-1414213562373095048, -18}, Number{-1414213562373095049, -18}, - Number{1999999999999999999, -18}}, + Number{1999999999999999999, -18}, + __LINE__}, {Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, - Number{10, 0}}, - // Maximum mantissa range - rounds down to maxMantissa/10e1 + Number{10, 0}, + __LINE__}, + // Maximum internal mantissa range - rounds down to + // maxMantissa/10e1 + // 99'999'999'999'999'999'800'000'000'000'000'000'100 + {Number{ + false, maxInternalMantissa, 0, Number::normalized{}}, + Number{ + false, maxInternalMantissa, 0, Number::normalized{}}, + Number{ + false, + maxInternalMantissa / 10 - 1, + 20, + Number::normalized{}}, + __LINE__}, + // Maximum actual mantissa range - same as int64 // 99'999'999'999'999'999'800'000'000'000'000'000'100 {Number{false, maxMantissa, 0, Number::normalized{}}, Number{false, maxMantissa, 0, Number::normalized{}}, - Number{ - false, - maxMantissa / 10 - 1, - 20, - Number::normalized{}}}, + Number{85'070'591'730'234'615'84, 19}, + __LINE__}, // Maximum int64 range // 85'070'591'730'234'615'847'396'907'784'232'501'249 - {Number{Number::maxRep, 0}, - Number{Number::maxRep, 0}, - Number{85'070'591'730'234'615'84, 19}}, + {Number{Number::largestMantissa, 0}, + Number{Number::largestMantissa, 0}, + Number{85'070'591'730'234'615'84, 19}, + __LINE__}, }); tests(cSmall, cLarge); } @@ -559,76 +623,100 @@ public: << " downward"; { auto const cSmall = std::to_array( - {{Number{7}, Number{8}, Number{56}}, + {{Number{7}, Number{8}, Number{56}, __LINE__}, {Number{1414213562373095, -15}, Number{1414213562373095, -15}, - Number{1999999999999999, -15}}, + Number{1999999999999999, -15}, + __LINE__}, {Number{-1414213562373095, -15}, Number{1414213562373095, -15}, - Number{-2000000000000000, -15}}, + Number{-2000000000000000, -15}, + __LINE__}, {Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, - Number{1999999999999999, -15}}, + Number{1999999999999999, -15}, + __LINE__}, {Number{3214285714285706, -15}, Number{3111111111111119, -15}, - Number{9999999999999999, -15}}, + Number{9999999999999999, -15}, + __LINE__}, {Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, - Number{0}}}); + Number{0}, + __LINE__}}); auto const cLarge = std::to_array( // 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{7}, Number{8}, Number{56}, __LINE__}, {Number{1414213562373095, -15}, Number{1414213562373095, -15}, - Number{1999999999999999861, -18}}, + Number{1999999999999999861, -18}, + __LINE__}, {Number{-1414213562373095, -15}, Number{1414213562373095, -15}, - Number{-1999999999999999862, -18}}, + Number{-1999999999999999862, -18}, + __LINE__}, {Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, - Number{1999999999999999861, -18}}, + Number{1999999999999999861, -18}, + __LINE__}, {Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{ false, 9'999'999'999'999'999'579ULL, -18, - Number::normalized{}}}, + Number::normalized{}}, + __LINE__}, {Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, - Number{0}}, + Number{0}, + __LINE__}, // 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{2, 0}, + __LINE__}, {Number{-1414213562373095048, -18}, Number{1414213562373095048, -18}, - Number{-1999999999999999998, -18}}, + Number{-1999999999999999998, -18}, + __LINE__}, {Number{-1414213562373095048, -18}, Number{-1414213562373095049, -18}, - Number{1999999999999999999, -18}}, + Number{1999999999999999999, -18}, + __LINE__}, {Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, - Number{10, 0}}, - // Maximum mantissa range - rounds down to maxMantissa/10e1 + Number{10, 0}, + __LINE__}, + // Maximum internal mantissa range - rounds down to + // maxMantissa/10-1 // 99'999'999'999'999'999'800'000'000'000'000'000'100 - {Number{false, maxMantissa, 0, Number::normalized{}}, - Number{false, maxMantissa, 0, Number::normalized{}}, + {Number{ + false, maxInternalMantissa, 0, Number::normalized{}}, + Number{ + false, maxInternalMantissa, 0, Number::normalized{}}, Number{ false, - maxMantissa / 10 - 1, + maxInternalMantissa / 10 - 1, 20, - Number::normalized{}}}, + Number::normalized{}}, + __LINE__}, + // Maximum mantissa range - same as int64 + {Number{false, maxMantissa, 0, Number::normalized{}}, + Number{false, maxMantissa, 0, Number::normalized{}}, + Number{85'070'591'730'234'615'84, 19}, + __LINE__}, // Maximum int64 range // 85'070'591'730'234'615'847'396'907'784'232'501'249 - {Number{Number::maxRep, 0}, - Number{Number::maxRep, 0}, - Number{85'070'591'730'234'615'84, 19}}, + {Number{Number::largestMantissa, 0}, + Number{Number::largestMantissa, 0}, + Number{85'070'591'730'234'615'84, 19}, + __LINE__}, }); tests(cSmall, cLarge); } @@ -637,68 +725,91 @@ public: << " upward"; { auto const cSmall = std::to_array( - {{Number{7}, Number{8}, Number{56}}, + {{Number{7}, Number{8}, Number{56}, __LINE__}, {Number{1414213562373095, -15}, Number{1414213562373095, -15}, - Number{2000000000000000, -15}}, + Number{2000000000000000, -15}, + __LINE__}, {Number{-1414213562373095, -15}, Number{1414213562373095, -15}, - Number{-1999999999999999, -15}}, + Number{-1999999999999999, -15}, + __LINE__}, {Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, - Number{2000000000000000, -15}}, + Number{2000000000000000, -15}, + __LINE__}, {Number{3214285714285706, -15}, Number{3111111111111119, -15}, - Number{1000000000000000, -14}}, + Number{1000000000000000, -14}, + __LINE__}, {Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, - Number{0}}}); + Number{0}, + __LINE__}}); auto const cLarge = std::to_array( // 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{7}, Number{8}, Number{56}, __LINE__}, {Number{1414213562373095, -15}, Number{1414213562373095, -15}, - Number{1999999999999999862, -18}}, + Number{1999999999999999862, -18}, + __LINE__}, {Number{-1414213562373095, -15}, Number{1414213562373095, -15}, - Number{-1999999999999999861, -18}}, + Number{-1999999999999999861, -18}, + __LINE__}, {Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, - Number{1999999999999999862, -18}}, + Number{1999999999999999862, -18}, + __LINE__}, {Number{3214285714285706, -15}, Number{3111111111111119, -15}, - Number{999999999999999958, -17}}, + Number{999999999999999958, -17}, + __LINE__}, {Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, - Number{0}}, + Number{0}, + __LINE__}, // 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{2000000000000000001, -18}, + __LINE__}, {Number{-1414213562373095048, -18}, Number{1414213562373095048, -18}, - Number{-1999999999999999997, -18}}, + Number{-1999999999999999997, -18}, + __LINE__}, {Number{-1414213562373095048, -18}, Number{-1414213562373095049, -18}, - Number{2, 0}}, + Number{2, 0}, + __LINE__}, {Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, - Number{1000000000000000001, -17}}, - // Maximum mantissa range - rounds up to minMantissa*10 - // 1e19*1e19=1e38 + Number{1000000000000000001, -17}, + __LINE__}, + // Maximum internal mantissa range - rounds up to + // minMantissa*10 1e19*1e19=1e38 + {Number{ + false, maxInternalMantissa, 0, Number::normalized{}}, + Number{ + false, maxInternalMantissa, 0, Number::normalized{}}, + Number{1, 38}, + __LINE__}, + // Maximum mantissa range - same as int64 {Number{false, maxMantissa, 0, Number::normalized{}}, Number{false, maxMantissa, 0, Number::normalized{}}, - Number{1, 38}}, + Number{85'070'591'730'234'615'85, 19}, + __LINE__}, // Maximum int64 range // 85'070'591'730'234'615'847'396'907'784'232'501'249 - {Number{Number::maxRep, 0}, - Number{Number::maxRep, 0}, - Number{85'070'591'730'234'615'85, 19}}, + {Number{Number::largestMantissa, 0}, + Number{Number::largestMantissa, 0}, + Number{85'070'591'730'234'615'85, 19}, + __LINE__}, }); tests(cSmall, cLarge); } @@ -971,6 +1082,13 @@ public: }; */ + auto const maxMantissa = Number::maxMantissa(); + auto const maxInternalMantissa = + static_cast( + static_cast(power(10, Number::mantissaLog()))) * + 10 - + 1; + auto const cSmall = std::to_array( {{Number{2}, 2, Number{1414213562373095049, -18}}, {Number{2'000'000}, 2, Number{1414213562373095049, -15}}, @@ -982,17 +1100,17 @@ public: {Number{0}, 5, Number{0}}, {Number{5625, -4}, 2, Number{75, -2}}}); auto const cLarge = std::to_array({ - {Number{false, Number::maxMantissa() - 9, -1, Number::normalized{}}, + {Number{false, maxInternalMantissa - 9, -1, Number::normalized{}}, 2, Number{false, 999'999'999'999'999'999, -9, Number::normalized{}}}, - {Number{false, Number::maxMantissa() - 9, 0, Number::normalized{}}, + {Number{false, maxInternalMantissa - 9, 0, Number::normalized{}}, 2, Number{ false, 3'162'277'660'168'379'330, -9, Number::normalized{}}}, - {Number{Number::maxRep}, + {Number{Number::largestMantissa}, 2, Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}}, - {Number{Number::maxRep}, + {Number{Number::largestMantissa}, 4, Number{false, 55'108'98747006743627, -14, Number::normalized{}}}, }); @@ -1051,7 +1169,7 @@ public: Number{5, -1}, Number{0}, Number{5625, -4}, - Number{Number::maxRep}, + Number{Number::largestMantissa}, }); test(cSmall); bool caught = false; @@ -1417,20 +1535,20 @@ public: case MantissaRange::large: // Test the edges // ((exponent < -(28)) || (exponent > -(8))))) - test(Number::min(), "1e-32750"); + test(Number::min(), "922337203685477581e-32768"); test(Number::max(), "9223372036854775807e32768"); test(Number::lowest(), "-9223372036854775807e32768"); { NumberRoundModeGuard mg(Number::towards_zero); auto const maxMantissa = Number::maxMantissa(); - BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL); + BEAST_EXPECT(maxMantissa == 9'223'372'036'854'775'807ULL); test( Number{false, maxMantissa, 0, Number::normalized{}}, - "9999999999999999990"); + "9223372036854775807"); test( Number{true, maxMantissa, 0, Number::normalized{}}, - "-9999999999999999990"); + "-9223372036854775807"); test( Number{std::numeric_limits::max(), 0}, @@ -1671,7 +1789,7 @@ public: Number const initalXrp{INITIAL_XRP}; BEAST_EXPECT(initalXrp.exponent() > 0); - Number const maxInt64{Number::maxRep}; + Number const maxInt64{Number::largestMantissa}; BEAST_EXPECT(maxInt64.exponent() > 0); // 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits BEAST_EXPECT( @@ -1691,7 +1809,7 @@ public: Number const initalXrp{INITIAL_XRP}; BEAST_EXPECT(initalXrp.exponent() <= 0); - Number const maxInt64{Number::maxRep}; + Number const maxInt64{Number::largestMantissa}; BEAST_EXPECT(maxInt64.exponent() <= 0); // 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits BEAST_EXPECT( @@ -1699,16 +1817,47 @@ public: NumberRoundModeGuard mg(Number::towards_zero); - auto const maxMantissa = Number::maxMantissa(); - Number const max = - Number{false, 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{false, maxMantissa / 10 - 1, 20, Number::normalized{}})); + { + auto const maxInternalMantissa = + static_cast(static_cast( + power(10, Number::mantissaLog()))) * + 10 - + 1; + + // Rounds down to fit under 2^63 + Number const max = + Number{false, maxInternalMantissa, 0, Number::normalized{}}; + // No alterations by the accessors + BEAST_EXPECT(max.mantissa() == maxInternalMantissa / 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{ + false, + maxInternalMantissa / 10 - 1, + 20, + Number::normalized{}})); + } + + { + auto const maxMantissa = Number::maxMantissa(); + Number const max = + Number{false, maxMantissa, 0, Number::normalized{}}; + // No alterations by the accessors + BEAST_EXPECT(max.mantissa() == maxMantissa); + BEAST_EXPECT(max.exponent() == 0); + // 85'070'591'730'234'615'847'396'907'784'232'501'249 - also 38 + // digits + BEAST_EXPECT( + (power(max, 2) == + Number{ + false, + 85'070'591'730'234'615'84, + 19, + Number::normalized{}})); + } } }