diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index f1841a7515..d0280f99b2 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -494,6 +494,9 @@ public: friend Number root2(Number f); + friend Number + power(Number const& f, unsigned n, unsigned d); + // Thread local rounding control. Default is to_nearest enum rounding_mode { to_nearest, towards_zero, downward, upward }; static rounding_mode @@ -589,6 +592,17 @@ private: // changing the values inside the range. static thread_local std::reference_wrapper range_; + // And one is needed because it needs to choose between oneSmall and + // oneLarge based on the current range + static Number + one(MantissaRange const& range); + + static Number + root(MantissaRange const& range, Number f, unsigned d); + + void + normalize(MantissaRange const& range); + void normalize(); @@ -616,6 +630,9 @@ private: MantissaRange::rep const& minMantissa, MantissaRange::rep const& maxMantissa); + bool + isnormal(MantissaRange const& range) const noexcept; + bool isnormal() const noexcept; @@ -632,6 +649,15 @@ private: static internalrep externalToInternal(rep mantissa); + /** Breaks down the number into components, potentially de-normalizing it. + * + * Ensures that the mantissa always has range_.log digits. + * + */ + template + std::tuple + toInternal(MantissaRange const& range) const; + /** Breaks down the number into components, potentially de-normalizing it. * * Ensures that the mantissa always has range_.log digits. @@ -641,6 +667,26 @@ private: std::tuple toInternal() const; + /** Rebuilds the number from components. + * + * If "normalized" is true, the values are expected to be normalized - all + * in their valid ranges. + * + * If "normalized" is false, the values are expected to be "near + * normalized", meaning that the mantissa has to be modified at most once to + * bring it back into range. + * + */ + template < + bool expectNormal = true, + detail::UnsignedMantissa Rep = internalrep> + void + fromInternal( + bool negative, + Rep mantissa, + int exponent, + MantissaRange const* pRange); + /** Rebuilds the number from components. * * If "normalized" is true, the values are expected to be normalized - all @@ -822,9 +868,8 @@ Number::lowest() noexcept } inline bool -Number::isnormal() const noexcept +Number::isnormal(MantissaRange const& range) const noexcept { - MantissaRange const& range = range_; auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_; return *this == Number{} || @@ -832,6 +877,12 @@ Number::isnormal() const noexcept minExponent <= exponent_ && exponent_ <= maxExponent); } +inline bool +Number::isnormal() const noexcept +{ + return isnormal(range_); +} + template std::pair Number::normalizeToRange(T minMantissa, T maxMantissa) const diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 1f42e3df48..daeaab0175 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -360,14 +360,13 @@ Number::externalToInternal(rep mantissa) */ template std::tuple -Number::toInternal() const +Number::toInternal(MantissaRange const& range) const { auto exponent = exponent_; bool const negative = mantissa_ < 0; auto const sign = negative ? -1 : 1; Rep mantissa = static_cast(sign * mantissa_); - auto const& range = Number::range_.get(); auto const referenceMin = range.referenceMin; auto const minMantissa = range.min; @@ -385,25 +384,43 @@ Number::toInternal() const return {negative, mantissa, exponent}; } +/** Breaks down the number into components, potentially de-normalizing it. + * + * Ensures that the mantissa always has range_.log digits. + * + */ +template +std::tuple +Number::toInternal() const +{ + return toInternal(range_); +} + /** Rebuilds the number from components. * - * If "normalized" is true, the values are expected to be normalized - all in - * their valid ranges. + * If "normalized" is true, the values are expected to be normalized - all + * in their valid ranges. * - * If "normalized" is false, the values are expected to be "near normalized", - * meaning that the mantissa has to be modified at most once to bring it back - * into range. + * If "normalized" is false, the values are expected to be "near + * normalized", meaning that the mantissa has to be modified at most once to + * bring it back into range. * */ template void -Number::fromInternal(bool negative, Rep mantissa, int exponent) +Number::fromInternal( + bool negative, + Rep mantissa, + int exponent, + MantissaRange const* pRange) { if constexpr (std::is_same_v< std::bool_constant, std::false_type>) { - auto const& range = Number::range_.get(); + if (!pRange) + throw std::runtime_error("Missing range to Number::fromInternal!"); + auto const& range = *pRange; auto const maxMantissa = range.max; auto const minMantissa = range.min; @@ -430,7 +447,34 @@ Number::fromInternal(bool negative, Rep mantissa, int exponent) exponent_ = exponent; XRPL_ASSERT_PARTS( - isnormal(), "ripple::Number::fromInternal", "Number is normalized"); + (pRange && isnormal(*pRange)) || isnormal(), + "ripple::Number::fromInternal", + "Number is normalized"); +} + +/** Rebuilds the number from components. + * + * If "normalized" is true, the values are expected to be normalized - all in + * their valid ranges. + * + * If "normalized" is false, the values are expected to be "near normalized", + * meaning that the mantissa has to be modified at most once to bring it back + * into range. + * + */ +template +void +Number::fromInternal(bool negative, Rep mantissa, int exponent) +{ + MantissaRange const* pRange = nullptr; + if constexpr (std::is_same_v< + std::bool_constant, + std::false_type>) + { + pRange = &Number::range_.get(); + } + + fromInternal(negative, mantissa, exponent, pRange); } constexpr Number @@ -457,13 +501,19 @@ Number::oneLarge() constexpr Number oneLrg = Number::oneLarge(); +Number +Number::one(MantissaRange const& range) +{ + if (&range == &smallRange) + return oneSml; + XRPL_ASSERT(&range == &largeRange, "Number::one() : valid range"); + return oneLrg; +} + Number Number::one() { - if (&range_.get() == &smallRange) - return oneSml; - XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_"); - return oneLrg; + return one(range_); } // Use the member names in this static function for now so the diff is cleaner @@ -576,15 +626,19 @@ Number::normalize( } void -Number::normalize() +Number::normalize(MantissaRange const& range) { - auto const& range = range_.get(); - - auto [negative, mantissa, exponent] = toInternal(); + auto [negative, mantissa, exponent] = toInternal(range); normalize(negative, mantissa, exponent, range.min, range.max); - fromInternal(negative, mantissa, exponent); + fromInternal(negative, mantissa, exponent, &range); +} + +void +Number::normalize() +{ + normalize(range_); } // Copy the number, but set a new exponent. Because the mantissa doesn't change, @@ -613,12 +667,14 @@ Number::Number(bool negative, internalrep mantissa, int exponent, normalized) { auto const& range = range_.get(); normalize(negative, mantissa, exponent, range.min, range.max); - fromInternal(negative, mantissa, exponent); + fromInternal(negative, mantissa, exponent, &range); } Number& Number::operator+=(Number const& y) { + auto const& range = range_.get(); + constexpr Number zero = Number{}; if (y == zero) return *this; @@ -634,7 +690,7 @@ Number::operator+=(Number const& y) } XRPL_ASSERT( - isnormal() && y.isnormal(), + isnormal(range) && y.isnormal(range), "xrpl::Number::operator+=(Number) : is normal"); // *n = negative // *m = mantissa @@ -642,9 +698,9 @@ Number::operator+=(Number const& y) // Need to use uint128_t, because large mantissas can overflow when added // together. - auto [xn, xm, xe] = toInternal(); + auto [xn, xm, xe] = toInternal(range); - auto [yn, ym, ye] = y.toInternal(); + auto [yn, ym, ye] = y.toInternal(range); Guard g; if (xe < ye) @@ -670,7 +726,6 @@ Number::operator+=(Number const& y) } while (xe > ye); } - auto const& range = range_.get(); auto const& minMantissa = range.min; auto const& maxMantissa = range.max; @@ -708,7 +763,7 @@ Number::operator+=(Number const& y) } normalize(xn, xm, xe, minMantissa, maxMantissa); - fromInternal(xn, xm, xe); + fromInternal(xn, xm, xe, &range); return *this; } @@ -743,6 +798,8 @@ divu10(uint128_t& u) Number& Number::operator*=(Number const& y) { + auto const& range = range_.get(); + constexpr Number zero = Number{}; if (*this == zero) return *this; @@ -756,10 +813,10 @@ Number::operator*=(Number const& y) // *m = mantissa // *e = exponent - auto [xn, xm, xe] = toInternal(); + auto [xn, xm, xe] = toInternal(range); int xs = xn ? -1 : 1; - auto [yn, ym, ye] = y.toInternal(); + auto [yn, ym, ye] = y.toInternal(range); int ys = yn ? -1 : 1; auto zm = uint128_t(xm) * uint128_t(ym); @@ -770,7 +827,6 @@ Number::operator*=(Number const& y) if (zn) g.set_negative(); - auto const& range = range_.get(); auto const& minMantissa = range.min; auto const& maxMantissa = range.max; @@ -793,13 +849,15 @@ Number::operator*=(Number const& y) "Number::multiplication overflow : exponent is " + std::to_string(xe)); normalize(zn, xm, xe, minMantissa, maxMantissa); - fromInternal(zn, xm, xe); + fromInternal(zn, xm, xe, &range); return *this; } Number& Number::operator/=(Number const& y) { + auto const& range = range_.get(); + constexpr Number zero = Number{}; if (y == zero) throw std::overflow_error("Number: divide by 0"); @@ -812,13 +870,12 @@ Number::operator/=(Number const& y) // *m = mantissa // *e = exponent - auto [np, nm, ne] = toInternal(); + auto [np, nm, ne] = toInternal(range); int ns = (np ? -1 : 1); - auto [dp, dm, de] = y.toInternal(); + auto [dp, dm, de] = y.toInternal(range); int ds = (dp ? -1 : 1); - auto const& range = range_.get(); auto const& minMantissa = range.min; auto const& maxMantissa = range.max; @@ -830,7 +887,7 @@ Number::operator/=(Number const& y) // f can be up to 10^(38-19) = 10^19 safely static_assert(smallRange.log == 15); static_assert(largeRange.log == 18); - bool small = Number::getMantissaScale() == MantissaRange::small; + bool small = range.scale == MantissaRange::small; uint128_t const f = small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL; XRPL_ASSERT_PARTS( @@ -882,9 +939,9 @@ Number::operator/=(Number const& y) } } normalize(zn, zm, ze, minMantissa, maxMantissa); - fromInternal(zn, zm, ze); + fromInternal(zn, zm, ze, &range); XRPL_ASSERT_PARTS( - isnormal(), "xrpl::Number::operator/=", "result is normalized"); + isnormal(range), "xrpl::Number::operator/=", "result is normalized"); return *this; } @@ -938,16 +995,18 @@ Number::truncate() const noexcept std::string to_string(Number const& amount) { + auto const& range = Number::range_.get(); + // keep full internal accuracy, but make more human friendly if possible constexpr Number zero = Number{}; if (amount == zero) return "0"; // The mantissa must have a set number of decimal places for this to work - auto [negative, mantissa, exponent] = amount.toInternal(); + auto [negative, mantissa, exponent] = amount.toInternal(range); // Use scientific notation for exponents that are too small or too large - auto const rangeLog = Number::mantissaLog(); + auto const rangeLog = range.log; if (((exponent != 0 && amount.exponent() != 0) && ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10))))) { @@ -1053,20 +1112,11 @@ power(Number const& f, unsigned n) return r; } -// Returns f^(1/d) -// Uses Newton–Raphson iterations until the result stops changing -// to find the non-negative root of the polynomial g(x) = x^d - f - -// This function, and power(Number f, unsigned n, unsigned d) -// treat corner cases such as 0 roots as advised by Annex F of -// the C standard, which itself is consistent with the IEEE -// floating point standards. - Number -root(Number f, unsigned d) +Number::root(MantissaRange const& range, Number f, unsigned d) { constexpr Number zero = Number{}; - auto const one = Number::one(); + auto const one = Number::one(range); if (f == one || d == 1) return f; @@ -1084,7 +1134,7 @@ root(Number f, unsigned d) return f; auto const [e, di] = [&]() { - auto const [negative, mantissa, exponent] = f.toInternal(); + auto const [negative, mantissa, exponent] = f.toInternal(range); // Scale f into the range (0, 1) such that the scale change (e) is a // multiple of the root (d) @@ -1106,7 +1156,7 @@ root(Number f, unsigned d) XRPL_ASSERT_PARTS( e % di == 0, "xrpl::root(Number, unsigned)", "e is divisible by d"); XRPL_ASSERT_PARTS( - f.isnormal(), "xrpl::root(Number, unsigned)", "f is normalized"); + f.isnormal(range), "xrpl::root(Number, unsigned)", "f is normalized"); bool neg = false; if (f < zero) { @@ -1140,17 +1190,34 @@ root(Number f, unsigned d) // return r * 10^(e/d) to reverse scaling auto const result = r.shiftExponent(e / di); XRPL_ASSERT_PARTS( - result.isnormal(), + result.isnormal(range), "xrpl::root(Number, unsigned)", "result is normalized"); return result; } +// Returns f^(1/d) +// Uses Newton–Raphson iterations until the result stops changing +// to find the non-negative root of the polynomial g(x) = x^d - f + +// This function, and power(Number f, unsigned n, unsigned d) +// treat corner cases such as 0 roots as advised by Annex F of +// the C standard, which itself is consistent with the IEEE +// floating point standards. + +Number +root(Number f, unsigned d) +{ + auto const& range = Number::range_.get(); + return Number::root(range, f, d); +} + Number root2(Number f) { + auto const& range = Number::range_.get(); constexpr Number zero = Number{}; - auto const one = Number::one(); + auto const one = Number::one(range); if (f == one) return f; @@ -1160,7 +1227,7 @@ root2(Number f) return f; auto const e = [&]() { - auto const [negative, mantissa, exponent] = f.toInternal(); + auto const [negative, mantissa, exponent] = f.toInternal(range); // Scale f into the range (0, 1) such that f's exponent is a // multiple of d @@ -1170,7 +1237,8 @@ root2(Number f) f = f.shiftExponent(-e); // f /= 10^e; return e; }(); - XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root2(Number)", "f is normalized"); + XRPL_ASSERT_PARTS( + f.isnormal(range), "xrpl::root2(Number)", "f is normalized"); // Quadratic least squares curve fit of f^(1/d) in the range [0, 1] auto const D = 105; @@ -1193,7 +1261,7 @@ root2(Number f) // return r * 10^(e/2) to reverse scaling auto const result = r.shiftExponent(e / 2); XRPL_ASSERT_PARTS( - result.isnormal(), "xrpl::root2(Number)", "result is normalized"); + result.isnormal(range), "xrpl::root2(Number)", "result is normalized"); return result; } @@ -1203,8 +1271,10 @@ root2(Number f) Number power(Number const& f, unsigned n, unsigned d) { + auto const& range = Number::range_.get(); + constexpr Number zero = Number{}; - auto const one = Number::one(); + auto const one = Number::one(range); if (f == one) return f; @@ -1226,7 +1296,7 @@ power(Number const& f, unsigned n, unsigned d) d /= g; if ((n % 2) == 1 && (d % 2) == 0 && f < zero) throw std::overflow_error("Number::power nan"); - return root(power(f, n), d); + return Number::root(range, power(f, n), d); } } // namespace ripple