diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index a6c00711ed..a1cf296aed 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -22,14 +22,14 @@ template constexpr std::optional logTen(T value) { - int power = 0; + int log = 0; while (value >= 10 && value % 10 == 0) { value /= 10; - ++power; + ++log; } if (value == 1) - return power; + return log; return std::nullopt; } @@ -50,16 +50,16 @@ using numberint128 = __int128_t; struct MantissaRange { - using rep = numberint128; + using internalrep = numberint128; - explicit constexpr MantissaRange(rep min_) - : min(min_), max(min_ * 10 - 1), power(logTen(min).value_or(-1)) + explicit constexpr MantissaRange(internalrep min_) + : min(min_), max(min_ * 10 - 1), log(logTen(min).value_or(-1)) { } - rep min; - rep max; - int power; + internalrep min; + internalrep max; + int log; }; class Number @@ -68,7 +68,7 @@ class Number using int128_t = numberint128; using rep = std::int64_t; - using internalrep = MantissaRange::rep; + using internalrep = MantissaRange::internalrep; internalrep mantissa_{0}; int exponent_{std::numeric_limits::lowest()}; @@ -121,11 +121,11 @@ public: operator/=(Number const& x); static constexpr Number - min(MantissaRange const& range) noexcept; + min() noexcept; static constexpr Number - max(MantissaRange const& range) noexcept; + max() noexcept; static constexpr Number - lowest(MantissaRange const& range) noexcept; + lowest() noexcept; /** Conversions to Number are implicit and conversions away from Number * are explicit. This design encourages and facilitates the use of Number @@ -194,7 +194,7 @@ public: while (ret.exponent_ < 0 && ret.mantissa_ != 0) { ret.exponent_ += 1; - ret.mantissa_ /= rep(10); + ret.mantissa_ /= internalrep(10); } // We are guaranteed that normalize() will never throw an exception // because exponent is either negative or zero at this point. @@ -249,17 +249,21 @@ public: return range_.get().max; } - inline static internalrep - mantissaPower() + inline static int + mantissaLog() { - return range_.get().power; + return range_.get().log; } + /// oneSmall is needed because the ranges are private constexpr static Number oneSmall(); + /// oneLarge is needed because the ranges are private constexpr static Number oneLarge(); + // And one is needed because it needs to choose between oneSmall and + // oneLarge based on the current range static Number one(); @@ -275,10 +279,12 @@ private: constexpr static MantissaRange smallRange{1'000'000'000'000'000LL}; static_assert(isPowerOfTen(smallRange.min)); static_assert(smallRange.max == 9'999'999'999'999'999LL); + static_assert(smallRange.log == 15); // maxint64 9,223,372,036,854,775,808 constexpr static MantissaRange largeRange{1'000'000'000'000'000'000LL}; static_assert(isPowerOfTen(largeRange.min)); static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL)); + static_assert(largeRange.log == 18); static_assert(largeRange.min < std::numeric_limits::max()); static_assert(largeRange.max > std::numeric_limits::max()); @@ -298,12 +304,7 @@ private: internalrep const& maxMantissa); constexpr bool - isnormal(MantissaRange const& range) const noexcept; - - friend Number - root(Number f, unsigned d); - friend Number - root2(Number f); + isnormal() const noexcept; class Guard; }; @@ -421,26 +422,27 @@ operator/(Number const& x, Number const& y) } inline constexpr Number -Number::min(MantissaRange const& range) noexcept +Number::min() noexcept { - return Number{range.min, minExponent, unchecked{}}; + return Number{range_.get().min, minExponent, unchecked{}}; } inline constexpr Number -Number::max(MantissaRange const& range) noexcept +Number::max() noexcept { - return Number{range.max, maxExponent, unchecked{}}; + return Number{range_.get().max, maxExponent, unchecked{}}; } inline constexpr Number -Number::lowest(MantissaRange const& range) noexcept +Number::lowest() noexcept { - return -Number{range.max, maxExponent, unchecked{}}; + return -Number{range_.get().max, maxExponent, unchecked{}}; } inline constexpr bool -Number::isnormal(MantissaRange const& range) const noexcept +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 && minExponent <= exponent_ && exponent_ <= maxExponent; diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index fc30bf7a74..e460db9ecc 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -14,7 +14,22 @@ #ifdef _MSC_VER #pragma message("Using boost::multiprecision::uint128_t") -#endif +#include +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 = typename uint128_t; +}; + +} // namespace std namespace ripple { @@ -61,14 +76,9 @@ public: is_negative() const noexcept; // add a digit + template void - push(numberuint128 d) noexcept; -#ifdef _MSC_VER - void - push(numberint128 d) noexcept; - void - push(std::int64_t d) noexcept; -#endif + push(T d) noexcept; // recover a digit unsigned @@ -79,6 +89,10 @@ public: // tie, round towards even. int round() noexcept; + +private: + void + doPush(unsigned d) noexcept; }; inline void @@ -100,29 +114,20 @@ Number::Guard::is_negative() const noexcept } inline void -Number::Guard::push(numberuint128 d128) noexcept +Number::Guard::doPush(unsigned d) noexcept { - unsigned d = static_cast(d128); - xbit_ = xbit_ || (digits_ & 0x0000'0000'0000'000F) != 0; digits_ >>= 4; digits_ |= (d & 0x0000'0000'0000'000FULL) << 60; } -#ifdef _MSC_VER -void -Number::Guard::push(numberint128 d) noexcept +template +inline void +Number::Guard::push(T d) noexcept { - push(static_cast(d)); + doPush(static_cast(d)); } -void -Number::Guard::push(std::int64_t d) noexcept -{ - push(static_cast(d)); -} -#endif - inline unsigned Number::Guard::pop() noexcept { @@ -178,23 +183,27 @@ constexpr Number Number::oneSmall() { return Number{ - Number::smallRange.min, -Number::smallRange.power, Number::unchecked{}}; + Number::smallRange.min, -Number::smallRange.log, Number::unchecked{}}; }; +constexpr Number oneSml = Number::oneSmall(); + constexpr Number Number::oneLarge() { return Number{ - Number::largeRange.min, -Number::largeRange.power, Number::unchecked{}}; + Number::largeRange.min, -Number::largeRange.log, Number::unchecked{}}; }; +constexpr Number oneLrg = Number::oneLarge(); + Number Number::one() { if (&range_.get() == &smallRange) - return oneSmall(); + return oneSml; XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_"); - return oneLarge(); + return oneLrg; } // Use the member names in this static function for now so the diff is cleaner @@ -205,17 +214,16 @@ Number::normalize( internalrep const& minMantissa, internalrep const& maxMantissa) { + constexpr Number zero = Number{}; if (mantissa_ == 0) { - constexpr Number zero = Number{}; mantissa_ = zero.mantissa_; exponent_ = zero.exponent_; return; } bool const negative = (mantissa_ < 0); - auto m = static_cast>(mantissa_); - if (negative) - m = -m; + auto m = static_cast>( + negative ? -mantissa_ : mantissa_); while ((m < minMantissa) && (exponent_ > minExponent)) { m *= 10; @@ -235,7 +243,6 @@ Number::normalize( mantissa_ = m; if ((exponent_ < minExponent) || (mantissa_ < minMantissa)) { - constexpr Number zero = Number{}; mantissa_ = zero.mantissa_; exponent_ = zero.exponent_; return; @@ -281,7 +288,7 @@ Number::operator+=(Number const& y) return *this; } XRPL_ASSERT( - isnormal(Number::range_) && y.isnormal(Number::range_), + isnormal() && y.isnormal(), "ripple::Number::operator+=(Number) : is normal"); auto xm = mantissa(); auto xe = exponent(); @@ -394,7 +401,7 @@ Number::operator+=(Number const& y) // Derived from Hacker's Delight Second Edition Chapter 10 // by Henry S. Warren, Jr. static inline unsigned -divu10(numberuint128& u) +divu10(uint128_t& u) { // q = u * 0.75 auto q = (u >> 1) + (u >> 2); @@ -426,7 +433,7 @@ Number::operator*=(Number const& y) return *this; } XRPL_ASSERT( - isnormal(Number::range_) && y.isnormal(Number::range_), + isnormal() && y.isnormal(), "ripple::Number::operator*=(Number) : is normal"); auto xm = mantissa(); auto xe = exponent(); @@ -444,7 +451,7 @@ Number::operator*=(Number const& y) ym = -ym; yn = -1; } - auto zm = numberuint128(xm) * numberuint128(ym); + auto zm = uint128_t(xm) * uint128_t(ym); auto ze = xe + ye; auto zn = xn * yn; Guard g; @@ -461,7 +468,7 @@ Number::operator*=(Number const& y) g.push(divu10(zm)); ++ze; } - xm = static_cast(zm); + xm = static_cast(zm); xe = ze; auto r = g.round(); if (r == 1 || (r == 0 && (xm & 1) == 1)) @@ -485,7 +492,7 @@ Number::operator*=(Number const& y) mantissa_ = xm * zn; exponent_ = xe; XRPL_ASSERT( - isnormal(Number::range_) || *this == Number{}, + isnormal() || *this == Number{}, "ripple::Number::operator*=(Number) : result is normal"); return *this; } @@ -514,12 +521,15 @@ Number::operator/=(Number const& y) dp = -1; } // Shift by 10^17 gives greatest precision while not overflowing - // numberuint128 or the cast back to int64_t + // uint128_t or the cast back to int64_t // TODO: Can/should this be made bigger for largeRange? - constexpr numberuint128 f = 100'000'000'000'000'000; + // log(2^127,10) ~ 38.2 + // largeRange.log = 18 + // f can be up to 10^(37-18) = 10^19 safely + constexpr uint128_t f = 100'000'000'000'000'000; static_assert(f == smallRange.min * 100); - static_assert(smallRange.power == 15); - mantissa_ = numberuint128(nm) * f / numberuint128(dm); + static_assert(smallRange.log == 15); + mantissa_ = uint128_t(nm) * f / uint128_t(dm); exponent_ = ne - de - 17; mantissa_ *= np * dp; normalize(); @@ -543,14 +553,19 @@ 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) throw std::overflow_error("Number::operator rep() overflow"); drops *= 10; } - if (drops > std::numeric_limits::max() / 10) - throw std::overflow_error("Number::operator rep() overflow"); auto r = g.round(); if (r == 1 || (r == 0 && (drops & 1) == 1)) { @@ -573,9 +588,9 @@ to_string(Number const& amount) auto mantissa = amount.mantissa(); // Use scientific notation for exponents that are too small or too large - auto const rangePower = Number::mantissaPower(); + auto const rangeLog = Number::mantissaLog(); if (((exponent != 0) && - ((exponent < -(rangePower + 10)) || (exponent > -(rangePower - 10))))) + ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10))))) { std::string ret = to_string(mantissa); ret.append(1, 'e'); @@ -591,11 +606,12 @@ to_string(Number const& amount) negative = true; } + // TODO: These numbers are probably wrong for largeRange XRPL_ASSERT( exponent + 43 > 0, "ripple::to_string(Number) : minimum exponent"); - ptrdiff_t const pad_prefix = 27; - ptrdiff_t const pad_suffix = 23; + ptrdiff_t const pad_prefix = Number::mantissaLog() + 12; + ptrdiff_t const pad_suffix = Number::mantissaLog() + 8; std::string const raw_value(to_string(mantissa)); std::string val; @@ -605,7 +621,7 @@ to_string(Number const& amount) val.append(raw_value); val.append(pad_suffix, '0'); - ptrdiff_t const offset(exponent + 43); + ptrdiff_t const offset(exponent + pad_prefix + 16); auto pre_from(val.begin()); auto const pre_to(val.begin() + offset); diff --git a/src/libxrpl/protocol/IOUAmount.cpp b/src/libxrpl/protocol/IOUAmount.cpp index ffa0ff6498..f69d6a3d98 100644 --- a/src/libxrpl/protocol/IOUAmount.cpp +++ b/src/libxrpl/protocol/IOUAmount.cpp @@ -1,8 +1,10 @@ +#include +// #include #include #include #include -#include +#include #include @@ -40,11 +42,13 @@ setSTNumberSwitchover(bool v) } /* The range for the mantissa when normalized */ -static std::int64_t constexpr minMantissa = 1'000'000'000'000'000ull; -static std::int64_t constexpr maxMantissa = minMantissa * 10 - 1; +// log(2^63,10) ~ 18.96 +// +static std::int64_t constexpr minMantissa = STAmount::cMinValue; +static std::int64_t constexpr maxMantissa = STAmount::cMaxValue; /* The range for the exponent when normalized */ -static int constexpr minExponent = -96; -static int constexpr maxExponent = 80; +static int constexpr minExponent = STAmount::cMinOffset; +static int constexpr maxExponent = STAmount::cMaxOffset; std::pair IOUAmount::scaleNumber(Number const& number) diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index ea9db187d5..ba6927ca22 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -310,8 +310,8 @@ STAmount& STAmount::operator=(IOUAmount const& iou) { XRPL_ASSERT( - native() == false, - "ripple::STAmount::operator=(IOUAmount) : is not XRP"); + integral() == false, + "ripple::STAmount::operator=(IOUAmount) : is not integral"); mOffset = iou.exponent(); mIsNegative = iou < beast::zero; if (mIsNegative) @@ -851,8 +851,9 @@ STAmount::canonicalize() if (getSTNumberSwitchover()) { + auto const value = unsafe_cast(mValue); Number num( - mIsNegative ? -mValue : mValue, mOffset, Number::unchecked{}); + mIsNegative ? -value : value, mOffset, Number::unchecked{}); auto set = [&](auto const& val) { mIsNegative = val.value() < 0; mValue = mIsNegative ? -val.value() : val.value(); diff --git a/src/libxrpl/protocol/STNumber.cpp b/src/libxrpl/protocol/STNumber.cpp index b68ac541d4..b02936709f 100644 --- a/src/libxrpl/protocol/STNumber.cpp +++ b/src/libxrpl/protocol/STNumber.cpp @@ -50,11 +50,25 @@ STNumber::add(Serializer& s) const XRPL_ASSERT( getFName().fieldType == getSType(), "ripple::STNumber::add : field type match"); - constexpr std::int64_t min = 100'000'000'000'000'000LL; - constexpr std::int64_t max = min * 10 - 1; - auto const [mantissa, exponent] = value_.normalizeToRange(min, max); - s.add64(mantissa); - s.add32(exponent); + 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; + auto const [mantissa, exponent] = value_.normalizeToRange(min, max); + s.add64(mantissa); + s.add32(exponent); + } } Number const& diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index 0c4e042259..9380e9c5e1 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -17,14 +17,15 @@ public: { testcase("zero"); - Number const z{0, 0}; + for (Number const& z : {Number{0, 0}, Number{0}}) + { + BEAST_EXPECT(z.mantissa() == 0); + BEAST_EXPECT(z.exponent() == Number{}.exponent()); - BEAST_EXPECT(z.mantissa() == 0); - BEAST_EXPECT(z.exponent() == Number{}.exponent()); - - BEAST_EXPECT((z + z) == z); - BEAST_EXPECT((z - z) == z); - BEAST_EXPECT(z == -z); + BEAST_EXPECT((z + z) == z); + BEAST_EXPECT((z - z) == z); + BEAST_EXPECT(z == -z); + } } void @@ -738,12 +739,12 @@ public: BEAST_EXPECT( std::numeric_limits::max() > INITIAL_XRP.drops()); - BEAST_EXPECT(Number::maxMantissa() > INITIAL_XRP.drops()); + BEAST_EXPECT(Number::maxMantissa() < INITIAL_XRP.drops()); Number initalXrp{INITIAL_XRP}; - BEAST_EXPECT(initalXrp.exponent() <= 0); + BEAST_EXPECT(initalXrp.exponent() > 0); Number maxInt64{std::numeric_limits::max()}; - BEAST_EXPECT(maxInt64.exponent() <= 0); + BEAST_EXPECT(maxInt64.exponent() > 0); // TODO: square maxInt64 and square Number::max()