From eaaea43f803572260595aea3c771cc8b69a0acda Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Thu, 27 Nov 2025 01:19:51 -0500 Subject: [PATCH] Refactor Number internals away from int128 to uint64 & a sign flag --- include/xrpl/basics/Number.h | 111 ++++++---- src/libxrpl/basics/Number.cpp | 320 +++++++++++++++------------- src/libxrpl/protocol/STAmount.cpp | 9 +- src/libxrpl/protocol/STNumber.cpp | 14 +- src/test/basics/Number_test.cpp | 174 +++++++++------ src/test/protocol/STNumber_test.cpp | 5 +- 6 files changed, 359 insertions(+), 274 deletions(-) diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index 5221576a6f..77f545b983 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -42,21 +42,20 @@ isPowerOfTen(T value) return logTen(value).has_value(); } -#ifdef _MSC_VER -using numberuint = boost::multiprecision::uint128_t; -using numberint = boost::multiprecision::int128_t; -#else // !defined(_MSC_VER) -using numberuint = __uint128_t; -using numberint = __int128_t; -#endif // !defined(_MSC_VER) +// #ifdef _MSC_VER +// using numberuint = boost::multiprecision::uint128_t; +// using numberint = boost::multiprecision::int128_t; +// #else // !defined(_MSC_VER) +// using numberuint = __uint128_t; +// using numberint = __int128_t; +// #endif // !defined(_MSC_VER) struct MantissaRange { - using rep = std::int64_t; - using internalrep = numberint; + using rep = std::uint64_t; enum mantissa_scale { small, large }; - explicit constexpr MantissaRange(mantissa_scale scale_, internalrep min_) + explicit constexpr MantissaRange(mantissa_scale scale_, rep min_) : min(min_) , max(min_ * 10 - 1) , log(logTen(min).value_or(-1)) @@ -65,19 +64,20 @@ struct MantissaRange } rep min; - internalrep max; + rep max; int log; mantissa_scale scale; }; class Number { - using uint128_t = numberuint; - using int128_t = numberint; + // using uint128_t = numberuint; + // using int128_t = numberint; - using rep = MantissaRange::rep; - using internalrep = MantissaRange::internalrep; + using rep = std::int64_t; + using internalrep = MantissaRange::rep; + bool negative_{false}; internalrep mantissa_{0}; int exponent_{std::numeric_limits::lowest()}; @@ -106,11 +106,16 @@ public: Number(rep mantissa); explicit Number(rep mantissa, int exponent); explicit constexpr Number( + bool negative, internalrep mantissa, int exponent, unchecked) noexcept; // Only unit tests are expected to use this ctor - explicit Number(internalrep mantissa, int exponent, normalized); + explicit Number( + bool negative, + internalrep mantissa, + int exponent, + normalized); constexpr rep mantissa() const noexcept; @@ -158,7 +163,8 @@ public: friend constexpr bool operator==(Number const& x, Number const& y) noexcept { - return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_; + return x.negative_ == y.negative_ && x.mantissa_ == y.mantissa_ && + x.exponent_ == y.exponent_; } friend constexpr bool @@ -166,14 +172,13 @@ public: { return !(x == y); } - friend constexpr bool operator<(Number const& x, Number const& y) noexcept { // If the two amounts have different signs (zero is treated as positive) // then the comparison is true iff the left is negative. - bool const lneg = x.mantissa_ < 0; - bool const rneg = y.mantissa_ < 0; + bool const lneg = x.negative_; + bool const rneg = y.negative_; if (lneg != rneg) return lneg; @@ -201,7 +206,7 @@ public: constexpr int signum() const noexcept { - return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0); + return negative_ ? -1 : (mantissa_ ? 1 : 0); } Number @@ -268,8 +273,7 @@ public: static void setMantissaScale(MantissaRange::mantissa_scale scale); - template - inline static T + inline static internalrep minMantissa() { return range_.get().min; @@ -308,8 +312,7 @@ private: static thread_local rounding_mode mode_; // The available ranges for mantissa - constexpr static internalrep maxRep = - std::numeric_limits::max(); + constexpr static internalrep maxRep = std::numeric_limits::max(); constexpr static MantissaRange smallRange{ MantissaRange::small, @@ -321,7 +324,7 @@ private: MantissaRange::large, 1'000'000'000'000'000'000LL}; static_assert(isPowerOfTen(largeRange.min)); - // maxRep 9,223,372,036,854,775,808 + // maxRep 9,223,372,036,854,775,807 static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL)); static_assert(largeRange.log == 18); static_assert(largeRange.min < maxRep); @@ -335,9 +338,11 @@ private: void normalize(); + template static void normalize( - internalrep& mantissa, + bool& negative, + T& mantissa, int& exponent, internalrep const& minMantissa, internalrep const& maxMantissa); @@ -355,23 +360,31 @@ private: }; inline constexpr Number::Number( + bool negative, internalrep mantissa, int exponent, unchecked) noexcept - : mantissa_{mantissa}, exponent_{exponent} + : negative_(negative), mantissa_{mantissa}, exponent_{exponent} { } -inline Number::Number(internalrep mantissa, int exponent, normalized) - : Number(mantissa, exponent, unchecked{}) +inline Number::Number( + bool negative, + internalrep mantissa, + int exponent, + normalized) + : Number(negative, mantissa, exponent, unchecked{}) { normalize(); } inline Number::Number(rep mantissa, int exponent) - : Number(mantissa, exponent, normalized{}) + : Number( + mantissa < 0, + mantissa < 0 ? -mantissa : mantissa, + exponent, + normalized{}) { - normalize(); } inline Number::Number(rep mantissa) : Number{mantissa, 0} @@ -382,7 +395,7 @@ inline constexpr Number::rep Number::mantissa() const noexcept { auto m = mantissa_; - while (m > maxRep) + if (m > maxRep) { XRPL_ASSERT_PARTS( !isnormal() || m % 10 == 0, @@ -390,7 +403,8 @@ Number::mantissa() const noexcept "large normalized mantissa has no remainder"); m /= 10; } - return static_cast(m); + auto const sign = negative_ ? -1 : 1; + return sign * static_cast(m); } inline constexpr int @@ -398,7 +412,7 @@ Number::exponent() const noexcept { auto m = mantissa_; auto e = exponent_; - while (m > maxRep) + if (m > maxRep) { XRPL_ASSERT_PARTS( !isnormal() || m % 10 == 0, @@ -419,8 +433,10 @@ Number::operator+() const noexcept inline constexpr Number Number::operator-() const noexcept { + if (mantissa_ == 0) + return Number{}; auto x = *this; - x.mantissa_ = -x.mantissa_; + x.negative_ = !x.negative_; return x; } @@ -495,40 +511,45 @@ operator/(Number const& x, Number const& y) inline Number Number::min() noexcept { - return Number{range_.get().min, minExponent, unchecked{}}; + return Number{false, range_.get().min, minExponent, unchecked{}}; } inline Number Number::max() noexcept { - return Number{range_.get().max, maxExponent, unchecked{}}; + return Number{ + false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}}; } inline Number Number::lowest() noexcept { - return -Number{range_.get().max, maxExponent, unchecked{}}; + return Number{ + true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}}; } inline bool 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; + 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); } template std::pair Number::normalizeToRange(T minMantissa, T maxMantissa) const { + bool negative = negative_; internalrep mantissa = mantissa_; int exponent = exponent_; - Number::normalize(mantissa, exponent, minMantissa, maxMantissa); + Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa); - return std::make_pair(static_cast(mantissa), exponent); + auto const sign = negative ? -1 : 1; + return std::make_pair(static_cast(sign * mantissa), exponent); } inline constexpr Number diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index c71b5a90a7..5da4a25d32 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -21,17 +21,17 @@ using uint128_t = boost::multiprecision::uint128_t; #else // !defined(_MSC_VER) using uint128_t = __uint128_t; #endif // !defined(_MSC_VER) -static_assert(std::is_same_v); - -namespace std { - -template <> -struct make_unsigned -{ - using type = uint128_t; -}; - -} // namespace std +// static_assert(std::is_same_v); +// +// namespace std { +// +// template <> +// struct make_unsigned +//{ +// using type = uint128_t; +// }; +// +// } // namespace std namespace ripple { @@ -60,7 +60,6 @@ Number::getMantissaScale() void Number::setMantissaScale(MantissaRange::mantissa_scale scale) { - // scale_ and range_ MUST stay in lockstep if (scale != MantissaRange::small && scale != MantissaRange::large) LogicError("Unknown mantissa scale"); range_ = scale == MantissaRange::small ? smallRange : largeRange; @@ -132,7 +131,7 @@ Number::Guard::is_negative() const noexcept inline void Number::Guard::doPush(unsigned d) noexcept { - xbit_ = xbit_ || (digits_ & 0x0000'0000'0000'000F) != 0; + xbit_ = xbit_ || ((digits_ & 0x0000'0000'0000'000F) != 0); digits_ >>= 4; digits_ |= (d & 0x0000'0000'0000'000FULL) << 60; } @@ -199,7 +198,10 @@ constexpr Number Number::oneSmall() { return Number{ - Number::smallRange.min, -Number::smallRange.log, Number::unchecked{}}; + false, + Number::smallRange.min, + -Number::smallRange.log, + Number::unchecked{}}; }; constexpr Number oneSml = Number::oneSmall(); @@ -208,7 +210,10 @@ constexpr Number Number::oneLarge() { return Number{ - Number::largeRange.min, -Number::largeRange.log, Number::unchecked{}}; + false, + Number::largeRange.min, + -Number::largeRange.log, + Number::unchecked{}}; }; constexpr Number oneLrg = Number::oneLarge(); @@ -223,9 +228,11 @@ Number::one() } // Use the member names in this static function for now so the diff is cleaner +template void Number::normalize( - internalrep& mantissa_, + bool& negative, + T& mantissa_, int& exponent_, internalrep const& minMantissa, internalrep const& maxMantissa) @@ -235,11 +242,10 @@ Number::normalize( { mantissa_ = zero.mantissa_; exponent_ = zero.exponent_; + negative = zero.negative_; return; } - bool const negative = (mantissa_ < 0); - auto m = static_cast>( - negative ? -mantissa_ : mantissa_); + auto m = mantissa_; while ((m < minMantissa) && (exponent_ > minExponent)) { m *= 10; @@ -260,6 +266,7 @@ Number::normalize( { mantissa_ = zero.mantissa_; exponent_ = zero.exponent_; + negative = zero.negative_; return; } // When using the largeRange, "m" needs fit within an int64, even if @@ -312,16 +319,17 @@ Number::normalize( mantissa_ >= minMantissa && mantissa_ <= maxMantissa, "ripple::Number::normalize", "final mantissa fits in range"); - - if (negative) - mantissa_ = -mantissa_; } void Number::normalize() { normalize( - mantissa_, exponent_, Number::minMantissa(), Number::maxMantissa()); + negative_, + mantissa_, + exponent_, + Number::minMantissa(), + Number::maxMantissa()); } // Copy the number, but set a new exponent. Because the mantissa doesn't change, @@ -339,7 +347,7 @@ Number::shiftExponent(int exponentDelta) const { return Number{}; } - Number const result{mantissa_, newExponent, unchecked{}}; + Number const result{negative_, mantissa_, newExponent, unchecked{}}; XRPL_ASSERT_PARTS( result.isnormal(), "ripple::Number::shiftExponent", @@ -350,41 +358,41 @@ Number::shiftExponent(int exponentDelta) const Number& Number::operator+=(Number const& y) { - if (y == Number{}) + constexpr Number zero = Number{}; + if (y == zero) return *this; - if (*this == Number{}) + if (*this == zero) { *this = y; return *this; } if (*this == -y) { - *this = Number{}; + *this = zero; return *this; } + XRPL_ASSERT( isnormal() && y.isnormal(), "ripple::Number::operator+=(Number) : is normal"); - auto xm = mantissa_; - auto xe = exponent_; - int xn = 1; - if (xm < 0) - { - xm = -xm; - xn = -1; - } - auto ym = y.mantissa_; - auto ye = y.exponent_; - int yn = 1; - if (ym < 0) - { - ym = -ym; - yn = -1; - } + // *n = negative + // *s = sign + // *m = mantissa + // *e = exponent + + bool xn = negative_; + int xs = xn ? -1 : 1; + internalrep xm = xs * mantissa(); + auto xe = exponent(); + + bool yn = y.negative_; + int ys = yn ? -1 : 1; + internalrep ym = ys * y.mantissa(); + auto ye = y.exponent(); Guard g; if (xe < ye) { - if (xn == -1) + if (xn) g.set_negative(); do { @@ -395,7 +403,7 @@ Number::operator+=(Number const& y) } else if (xe > ye) { - if (yn == -1) + if (yn) g.set_negative(); do { @@ -404,12 +412,15 @@ Number::operator+=(Number const& y) ++ye; } while (xe > ye); } + + auto const minMantissa = Number::minMantissa(); + if (xn == yn) { auto const maxMantissa = Number::maxMantissa(); xm += ym; - if (xm > maxMantissa) + if (xm > maxMantissa || xm > maxRep) { g.push(xm % 10); xm /= 10; @@ -419,7 +430,7 @@ Number::operator+=(Number const& y) if (r == 1 || (r == 0 && (xm & 1) == 1)) { ++xm; - if (xm > maxMantissa) + if (xm > maxMantissa || xm > maxRep) { xm /= 10; ++xe; @@ -430,8 +441,6 @@ Number::operator+=(Number const& y) } else { - auto const minMantissa = Number::minMantissa(); - if (xm > ym) { xm = xm - ym; @@ -442,7 +451,7 @@ Number::operator+=(Number const& y) xe = ye; xn = yn; } - while (xm < minMantissa) + while (xm < minMantissa && xm * 10 <= maxRep) { xm *= 10; xm -= g.pop(); @@ -452,24 +461,32 @@ Number::operator+=(Number const& y) if (r == 1 || (r == 0 && (xm & 1) == 1)) { --xm; - if (xm < minMantissa) + if (xm < minMantissa && xm * 10 <= maxRep) { xm *= 10; --xe; } } - if (xe < minExponent) - { - xm = 0; - xe = Number{}.exponent_; - } } - mantissa_ = xm * xn; + // Bring xm into the minMantissa / maxMantissa range AFTER rounding + if (xm < minMantissa) + { + xm *= 10; + --xe; + } + if (xe < minExponent) + { + xm = zero.mantissa_; + xe = zero.exponent_; + xn = zero.negative_; + } + + negative_ = xn; + mantissa_ = xm; exponent_ = xe; - normalize(); XRPL_ASSERT( - isnormal() || *this == Number{}, - "ripple::Number::operator+=(Number) : result is normal"); + isnormal(), "ripple::Number::operator+=(Number) : result is normal"); + normalize(); return *this; } @@ -504,9 +521,10 @@ divu10(uint128_t& u) Number& Number::operator*=(Number const& y) { - if (*this == Number{}) + constexpr Number zero = Number{}; + if (*this == zero) return *this; - if (y == Number{}) + if (y == zero) { *this = y; return *this; @@ -514,32 +532,33 @@ Number::operator*=(Number const& y) XRPL_ASSERT( isnormal() && y.isnormal(), "ripple::Number::operator*=(Number) : is normal"); - auto xm = mantissa_; - auto xe = exponent_; - int xn = 1; - if (xm < 0) - { - xm = -xm; - xn = -1; - } - auto ym = y.mantissa_; - auto ye = y.exponent_; - int yn = 1; - if (ym < 0) - { - ym = -ym; - yn = -1; - } + // *n = negative + // *s = sign + // *m = mantissa + // *e = exponent + + bool xn = negative_; + int xs = xn ? -1 : 1; + internalrep xm = xs * mantissa(); + auto xe = exponent(); + + bool yn = y.negative_; + int ys = yn ? -1 : 1; + internalrep ym = ys * y.mantissa(); + auto ye = y.exponent(); + auto zm = uint128_t(xm) * uint128_t(ym); auto ze = xe + ye; - auto zn = xn * yn; + auto zs = xs * ys; + bool zn = (zs == -1); Guard g; - if (zn == -1) + if (zn) g.set_negative(); + auto const minMantissa = Number::minMantissa(); auto const maxMantissa = Number::maxMantissa(); - while (zm > maxMantissa) + while (zm > maxMantissa || zm > maxRep) { // The following is optimization for: // g.push(static_cast(zm % 10)); @@ -553,53 +572,62 @@ Number::operator*=(Number const& y) if (r == 1 || (r == 0 && (xm & 1) == 1)) { ++xm; - if (xm > maxMantissa) + if (xm > maxMantissa || xm > maxRep) { xm /= 10; ++xe; } } + // Bring xm back into the minMantissa / maxMantissa range + if (xm < minMantissa) + { + xm *= 10; + --xe; + } if (xe < minExponent) { - xm = 0; - xe = Number{}.exponent_; + xm = zero.mantissa_; + xe = zero.exponent_; + zn = zero.negative_; } if (xe > maxExponent) throw std::overflow_error( "Number::multiplication overflow : exponent is " + std::to_string(xe)); - mantissa_ = xm * zn; + negative_ = zn; + mantissa_ = xm; exponent_ = xe; - normalize(); XRPL_ASSERT( - isnormal() || *this == Number{}, - "ripple::Number::operator*=(Number) : result is normal"); + isnormal(), "ripple::Number::operator*=(Number) : result is normal"); + normalize(); return *this; } Number& Number::operator/=(Number const& y) { - if (y == Number{}) + constexpr Number zero = Number{}; + if (y == zero) throw std::overflow_error("Number: divide by 0"); - if (*this == Number{}) + if (*this == zero) return *this; - int np = 1; + // n* = numerator + // d* = denominator + // *p = negative (positive?) + // *s = sign + // *m = mantissa + // *e = exponent + + bool np = negative_; + int ns = (np ? -1 : 1); auto nm = mantissa_; auto ne = exponent_; - if (nm < 0) - { - nm = -nm; - np = -1; - } - int dp = 1; + + bool dp = y.negative_; + int ds = (dp ? -1 : 1); auto dm = y.mantissa_; auto de = y.exponent_; - if (dm < 0) - { - dm = -dm; - dp = -1; - } + // Shift by 10^17 gives greatest precision while not overflowing // uint128_t or the cast back to int64_t // TODO: Can/should this be made bigger for largeRange? @@ -612,7 +640,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"); @@ -620,14 +648,15 @@ Number::operator/=(Number const& y) auto const dmu = static_cast(dm); // correctionFactor can be anything between 10 and f, depending on how much // extra precision we want to only use for rounding with the - // largeMantissa. Three digits seems like plenty, and is more than - // the smallMantissa uses. + // largeRange. Three digits seems like plenty, and is more than + // the smallRange uses. uint128_t const correctionFactor = 1'000; auto const numerator = uint128_t(nm) * f; - mantissa_ = numerator / dmu; - exponent_ = ne - de - (small ? 17 : 19); + auto zm = numerator / dmu; + auto ze = ne - de - (small ? 17 : 19); + bool zn = (ns * ds) < 0; if (!small) { // Virtually multiply numerator by correctionFactor. Since that would @@ -652,27 +681,31 @@ Number::operator/=(Number const& y) auto const remainder = (numerator % dmu); if (remainder != 0) { - mantissa_ *= correctionFactor; + zm *= correctionFactor; auto const correction = remainder * correctionFactor / dmu; - mantissa_ += correction; + zm += correction; // divide by 1000 by moving the exponent, so we don't lose the // integer value we just computed - exponent_ -= 3; + ze -= 3; } } - mantissa_ *= np * dp; - normalize(); + normalize(zn, zm, ze, minMantissa(), maxMantissa()); + negative_ = zn; + mantissa_ = static_cast(zm); + exponent_ = ze; + XRPL_ASSERT_PARTS( + isnormal(), "ripple::Number::operator/=", "result is normalized"); return *this; } Number::operator rep() const { - internalrep drops = mantissa_; - int offset = exponent_; + rep drops = mantissa(); + int offset = exponent(); Guard g; if (drops != 0) { - if (drops < 0) + if (negative_) { g.set_negative(); drops = -drops; @@ -682,13 +715,6 @@ Number::operator rep() const g.push(drops % 10); drops /= 10; } - if (offset == 0 && drops > std::numeric_limits::max()) - { - // If offset == 0, then the loop won't run, and the overflow check - // won't be made, but a int128 can overflow int64 by itself, so - // check here. - throw std::overflow_error("Number::operator rep() overflow"); - } for (; offset > 0; --offset) { if (drops > std::numeric_limits::max() / 10) @@ -703,37 +729,27 @@ Number::operator rep() const if (g.is_negative()) drops = -drops; } - return static_cast(drops); + return drops; } std::string to_string(Number const& amount) { // keep full internal accuracy, but make more human friendly if possible - if (amount == Number{}) + constexpr Number zero = Number{}; + if (amount == zero) return "0"; auto const exponent = amount.exponent_; - - bool const negative = amount.mantissa_ < 0; - - auto const mantissa = [&]() { - auto mantissa = amount.mantissa_; - if (negative) - { - mantissa = -mantissa; - } - XRPL_ASSERT( - mantissa < std::numeric_limits::max(), - "ripple::to_string(Number) : mantissa fits in uin64_t"); - return static_cast(mantissa); - }(); + auto const mantissa = amount.mantissa_; + bool const negative = amount.negative_; // Use scientific notation for exponents that are too small or too large auto const rangeLog = Number::mantissaLog(); if (((exponent != 0) && ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10))))) { + // TODO: trim trailing zeros off mantissa std::string ret = negative ? "-" : ""; ret.append(std::to_string(mantissa)); ret.append(1, 'e'); @@ -741,14 +757,11 @@ to_string(Number const& amount) return ret; } - // TODO: These numbers are probably wrong for largeRange XRPL_ASSERT( exponent + 43 > 0, "ripple::to_string(Number) : minimum exponent"); - auto const mantissaLog = Number::mantissaLog(); - - ptrdiff_t const pad_prefix = mantissaLog + 12; - ptrdiff_t const pad_suffix = mantissaLog + 8; + ptrdiff_t const pad_prefix = rangeLog + 12; + ptrdiff_t const pad_suffix = rangeLog + 8; std::string const raw_value(std::to_string(mantissa)); std::string val; @@ -758,7 +771,7 @@ to_string(Number const& amount) val.append(raw_value); val.append(pad_suffix, '0'); - ptrdiff_t const offset(exponent + pad_prefix + mantissaLog + 1); + ptrdiff_t const offset(exponent + pad_prefix + rangeLog + 1); auto pre_from(val.begin()); auto const pre_to(val.begin() + offset); @@ -841,6 +854,7 @@ power(Number const& f, unsigned n) Number root(Number f, unsigned d) { + constexpr Number zero = Number{}; auto const one = Number::one(); if (f == one || d == 1) @@ -850,12 +864,12 @@ root(Number f, unsigned d) if (f == -one) return one; if (abs(f) < one) - return Number{}; + return zero; throw std::overflow_error("Number::root infinity"); } - if (f < Number{} && d % 2 == 0) + if (f < zero && d % 2 == 0) throw std::overflow_error("Number::root nan"); - if (f == Number{}) + if (f == zero) return f; // Scale f into the range (0, 1) such that f's exponent is a multiple of d @@ -874,7 +888,7 @@ root(Number f, unsigned d) XRPL_ASSERT_PARTS( f.isnormal(), "ripple::root(Number, unsigned)", "f is normalized"); bool neg = false; - if (f < Number{}) + if (f < zero) { neg = true; f = -f; @@ -915,13 +929,14 @@ root(Number f, unsigned d) Number root2(Number f) { + constexpr Number zero = Number{}; auto const one = Number::one(); if (f == one) return f; - if (f < Number{}) + if (f < zero) throw std::overflow_error("Number::root nan"); - if (f == Number{}) + if (f == zero) return f; // Scale f into the range (0, 1) such that f's exponent is a multiple of d @@ -962,6 +977,7 @@ root2(Number f) Number power(Number const& f, unsigned n, unsigned d) { + constexpr Number zero = Number{}; auto const one = Number::one(); if (f == one) @@ -974,7 +990,7 @@ power(Number const& f, unsigned n, unsigned d) if (f == -one) return one; if (abs(f) < one) - return Number{}; + return zero; // abs(f) > one throw std::overflow_error("Number::power infinity"); } @@ -982,7 +998,7 @@ power(Number const& f, unsigned n, unsigned d) return one; n /= g; d /= g; - if ((n % 2) == 1 && (d % 2) == 0 && f < Number{}) + if ((n % 2) == 1 && (d % 2) == 0 && f < zero) throw std::overflow_error("Number::power nan"); return root(power(f, n), d); } diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index ba6927ca22..5285f0f374 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -851,12 +851,11 @@ STAmount::canonicalize() if (getSTNumberSwitchover()) { - auto const value = unsafe_cast(mValue); - Number num( - mIsNegative ? -value : value, mOffset, Number::unchecked{}); + Number num(mIsNegative, mValue, mOffset, Number::unchecked{}); auto set = [&](auto const& val) { - mIsNegative = val.value() < 0; - mValue = mIsNegative ? -val.value() : val.value(); + auto const value = val.value(); + mIsNegative = value < 0; + mValue = mIsNegative ? -value : value; }; if (native()) set(XRPAmount{num}); diff --git a/src/libxrpl/protocol/STNumber.cpp b/src/libxrpl/protocol/STNumber.cpp index eee35737f5..9e9e51cafb 100644 --- a/src/libxrpl/protocol/STNumber.cpp +++ b/src/libxrpl/protocol/STNumber.cpp @@ -191,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 @@ -199,11 +199,13 @@ numberFromJson(SField const& field, Json::Value const& value) Throw("not a number"); } - numberint mantissa = parts.mantissa; - if (parts.negative) - mantissa = -mantissa; - - return STNumber{field, Number{mantissa, parts.exponent, Number::normalized{}}}; + return STNumber{ + field, + Number{ + parts.negative, + parts.mantissa, + parts.exponent, + Number::normalized{}}}; } } // namespace ripple diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index bab7bd06dc..020146607d 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -34,10 +34,11 @@ 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 = Number{minMantissa * 10, 32768, Number::normalized{}}; + Number x = + Number{false, minMantissa * 10, 32768, Number::normalized{}}; } catch (std::overflow_error const&) { @@ -53,12 +54,14 @@ public: }; test( - Number{minMantissa * 10, 32767, Number::normalized{}}, - Number{minMantissa, 32768, Number::normalized{}}); - test(Number{minMantissa, -32769, Number::normalized{}}, Number{}); + Number{false, minMantissa * 10, 32767, Number::normalized{}}, + Number{false, minMantissa, 32768, Number::normalized{}}); test( - Number{minMantissa * 1'000 + 1'500, 32000, Number::normalized{}}, - Number{minMantissa + 2, 32003, Number::normalized{}}); + Number{false, minMantissa, -32769, Number::normalized{}}, Number{}); + test( + Number{false, minMantissa, 32000, Number::normalized{}} * 1'000 + + Number{false, 1'500, 32000, Number::normalized{}}, + Number{false, minMantissa + 2, 32003, Number::normalized{}}); // 9,223,372,036,854,775,808 test( @@ -79,7 +82,7 @@ public: try { Number q = - Number{minMantissa * 100 - 1, 32767, Number::normalized{}}; + Number{false, minMantissa, 32767, Number::normalized{}} * 100; } catch (std::overflow_error const&) { @@ -129,13 +132,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), + true, + 9'999'999'999'999'344'444ULL, -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, + false, + 9'999'999'999'999'344'444ULL, -19, Number::normalized{}}}, {Number{}, Number{5}, Number{5}}, @@ -156,13 +161,15 @@ 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), + true, + 9'999'999'999'999'999'344ULL, -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, + false, + 9'999'999'999'999'999'344ULL, -19, Number::normalized{}}}, {Number{}, Number{5}, Number{5}}, @@ -170,12 +177,14 @@ public: Number{-5'555'555'555'555'555'554, -32768}, Number{0}}, {Number{ - -(numberint(9'999'999'999'999'999) * 1'000 + 999), + true, + 9'999'999'999'999'999'999ULL, -37, Number::normalized{}}, Number{1'000'000'000'000'000'000, -18}, Number{ - numberint(9'999'999'999'999'999) * 1'000 + 990, + false, + 9'999'999'999'999'999'990ULL, -19, Number::normalized{}}}}); auto test = [this](auto const& c) { @@ -195,8 +204,14 @@ public: bool caught = false; try { - Number{Number::maxMantissa(), 32768, Number::normalized{}} + - Number{Number::minMantissa() * 5, 32767}; + Number{ + false, Number::maxMantissa(), 32768, Number::normalized{}} + + Number{ + false, + Number::minMantissa(), + 32767, + Number::normalized{}} * + 5; } catch (std::overflow_error const&) { @@ -236,13 +251,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, + false, + 9'999'999'999'999'344'444ULL, -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), + true, + 9'999'999'999'999'344'444ULL, -19, Number::normalized{}}}, {Number{1'000'000'000'000'000, -15}, @@ -258,13 +275,15 @@ public: {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, + false, + 9'999'999'999'999'344'444ULL, -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), + true, + 9'999'999'999'999'344'444ULL, -19, Number::normalized{}}}, {Number{1'000'000'000'000'000'000, -18}, @@ -357,7 +376,8 @@ public: {Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{ - numberint(9'999'999'999'999'999) * 1000 + 579, + false, + 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}}, {Number{1000000000000000000, -32768}, @@ -379,8 +399,8 @@ public: Number{3111111111111111119, -18}, Number{10, 0}}, // Maximum mantissa range - rounds up to 1e19 - {Number{maxMantissa, 0, Number::normalized{}}, - Number{maxMantissa, 0, Number::normalized{}}, + {Number{false, maxMantissa, 0, Number::normalized{}}, + Number{false, maxMantissa, 0, Number::normalized{}}, Number{1, 38}}, // Maximum int64 range {Number{maxInt64, 0}, @@ -428,7 +448,8 @@ public: {Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{ - numberint(9999999999999999) * 1000 + 579, + false, + 9999999999999999579ULL, -18, Number::normalized{}}}, {Number{1000000000000000000, -32768}, @@ -451,9 +472,13 @@ public: 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{}}}, + {Number{false, maxMantissa, 0, Number::normalized{}}, + Number{false, maxMantissa, 0, Number::normalized{}}, + Number{ + false, + 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}, @@ -501,7 +526,8 @@ public: {Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{ - numberint(9'999'999'999'999'999) * 1000 + 579, + false, + 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}}, {Number{1000000000000000000, -32768}, @@ -524,9 +550,13 @@ public: 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{}}}, + {Number{false, maxMantissa, 0, Number::normalized{}}, + Number{false, maxMantissa, 0, Number::normalized{}}, + Number{ + false, + 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}, @@ -594,8 +624,8 @@ public: 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{false, maxMantissa, 0, Number::normalized{}}, + Number{false, maxMantissa, 0, Number::normalized{}}, Number{1, 38}}, // Maximum int64 range // 85'070'591'730'234'615'847'396'907'784'232'501'249 @@ -611,8 +641,12 @@ public: bool caught = false; try { - Number{maxMantissa, 32768, Number::normalized{}} * - Number{Number::minMantissa() * 5, 32767}; + Number{false, maxMantissa, 32768, Number::normalized{}} * + Number{ + false, + Number::minMantissa() * 5, + 32767, + Number::normalized{}}; } catch (std::overflow_error const&) { @@ -685,9 +719,9 @@ public: {Number{1414213562373095049, -13}, Number{1414213562373095049, -13}, Number{1}}, - {Number{maxMantissa, 0, Number::normalized{}}, + {Number{false, maxMantissa, 0, Number::normalized{}}, Number{1'000'000'000'000'000'000}, - Number{maxMantissa, -18, Number::normalized{}}}}); + Number{false, maxMantissa, -18, Number::normalized{}}}}); tests(cSmall, cLarge); } testcase << "test_div " << to_string(Number::getMantissaScale()) @@ -732,9 +766,9 @@ public: {Number{1414213562373095049, -13}, Number{1414213562373095049, -13}, Number{1}}, - {Number{maxMantissa, 0, Number::normalized{}}, + {Number{false, maxMantissa, 0, Number::normalized{}}, Number{1'000'000'000'000'000'000}, - Number{maxMantissa, -18, Number::normalized{}}}}); + Number{false, maxMantissa, -18, Number::normalized{}}}}); tests(cSmall, cLarge); } testcase << "test_div " << to_string(Number::getMantissaScale()) @@ -779,9 +813,9 @@ public: {Number{1414213562373095049, -13}, Number{1414213562373095049, -13}, Number{1}}, - {Number{maxMantissa, 0, Number::normalized{}}, + {Number{false, maxMantissa, 0, Number::normalized{}}, Number{1'000'000'000'000'000'000}, - Number{maxMantissa, -18, Number::normalized{}}}}); + Number{false, maxMantissa, -18, Number::normalized{}}}}); tests(cSmall, cLarge); } testcase << "test_div " << to_string(Number::getMantissaScale()) @@ -826,9 +860,9 @@ public: {Number{1414213562373095049, -13}, Number{1414213562373095049, -13}, Number{1}}, - {Number{maxMantissa, 0, Number::normalized{}}, + {Number{false, maxMantissa, 0, Number::normalized{}}, Number{1'000'000'000'000'000'000}, - Number{maxMantissa, -18, Number::normalized{}}}}); + Number{false, maxMantissa, -18, Number::normalized{}}}}); tests(cSmall, cLarge); } testcase << "test_div " << to_string(Number::getMantissaScale()) @@ -880,7 +914,21 @@ public: {Number{5, -1}, 0, Number{0}}, {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{}}, + 2, + Number{false, 999'999'999'999'999'999, -9, Number::normalized{}}}, + {Number{false, Number::maxMantissa() - 9, 0, Number::normalized{}}, + 2, + Number{ + false, 3'162'277'660'168'379'330, -9, Number::normalized{}}}, + }); test(cSmall); + if (Number::getMantissaScale() != MantissaRange::small) + { + NumberRoundModeGuard mg(Number::towards_zero); + test(cLarge); + } bool caught = false; try { @@ -957,16 +1005,10 @@ public: {Number{-64}, 3, Number{-262144}}, {Number{64}, 11, - Number{ - numberint(73786976294838206) * 1000 + 464, - 0, - Number::normalized{}}}, + Number{false, 7378697629483820646ULL, 1, Number::normalized{}}}, {Number{-64}, 11, - -(Number{ - numberint(73786976294838206) * 1000 + 464, - 0, - Number::normalized{}})}}; + Number{true, 7378697629483820646ULL, 1, Number::normalized{}}}}; for (auto const& [x, y, z] : c) BEAST_EXPECT((power(x, y) == z)); } @@ -1267,13 +1309,17 @@ public: BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999); test( Number{ - maxMantissa * 1000 + 999, -3, Number::normalized()}, - "9999999999999999"); - test( - -(Number{ + false, maxMantissa * 1000 + 999, -3, - Number::normalized()}), + Number::normalized()}, + "9999999999999999"); + test( + Number{ + true, + maxMantissa * 1000 + 999, + -3, + Number::normalized()}, "-9999999999999999"); test( @@ -1296,18 +1342,18 @@ public: test(Number(-2, 11), "-2000000000000000000e-7"); test(Number::min(), "1000000000000000000e-32768"); - test(Number::max(), "9999999999999999999e32768"); - test(Number::lowest(), "-9999999999999999999e32768"); + 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); test( - Number{maxMantissa, 0, Number::normalized{}}, + Number{false, maxMantissa, 0, Number::normalized{}}, "9999999999999999990"); test( - -(Number{maxMantissa, 0, Number::normalized{}}), + Number{true, maxMantissa, 0, Number::normalized{}}, "-9999999999999999990"); test( @@ -1430,7 +1476,7 @@ public: (power(maxInt64, 2) == Number{85'070'591'730'234'62, 22})); Number const max = - Number{Number::maxMantissa(), 0, Number::normalized{}}; + Number{false, 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})); @@ -1452,15 +1498,15 @@ public: NumberRoundModeGuard mg(Number::towards_zero); auto const maxMantissa = Number::maxMantissa(); - Number const max = Number{maxMantissa, 0, Number::normalized{}}; + 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{ - maxMantissa / 10 - 1, 20, Number::normalized{}})); + Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}})); } } diff --git a/src/test/protocol/STNumber_test.cpp b/src/test/protocol/STNumber_test.cpp index 1192b2c4a4..58a385d99f 100644 --- a/src/test/protocol/STNumber_test.cpp +++ b/src/test/protocol/STNumber_test.cpp @@ -152,8 +152,9 @@ struct STNumber_test : public beast::unit_test::suite numberFromJson(sfNumber, minInt) == STNumber( sfNumber, - -Number{ - numberint(9'223'372'036'854'775) * 1000 + 808, + Number{ + true, + 9'223'372'036'854'775'808ULL, 0, Number::normalized{}})); }