|
|
|
|
@@ -7,7 +7,9 @@
|
|
|
|
|
#include <limits>
|
|
|
|
|
#include <optional>
|
|
|
|
|
#include <ostream>
|
|
|
|
|
#include <set>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
|
|
|
|
|
namespace xrpl {
|
|
|
|
|
|
|
|
|
|
@@ -44,11 +46,11 @@ isPowerOfTen(T value)
|
|
|
|
|
* * 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.
|
|
|
|
|
* The MantissaScale enum indicates properties of the range: size, and some behavioral
|
|
|
|
|
* options. This intentionally restricts the number of unique MantissaRanges that can
|
|
|
|
|
* be instantiated: one for each scale.
|
|
|
|
|
*
|
|
|
|
|
* The "small" scale is based on the behavior of STAmount for IOUs. It has a min
|
|
|
|
|
* 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
|
|
|
|
|
* uses before Lending Protocol was implemented, mostly related to AMM.
|
|
|
|
|
*
|
|
|
|
|
@@ -59,29 +61,54 @@ isPowerOfTen(T value)
|
|
|
|
|
* STNumber field type, and for internal calculations. That necessitated the
|
|
|
|
|
* "large" scale.
|
|
|
|
|
*
|
|
|
|
|
* The "large" scale is intended to represent all values that can be represented
|
|
|
|
|
* The "Large" scales are 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.
|
|
|
|
|
* value of 10^19-1. "LargeLegacy" is like "Large", but preserves
|
|
|
|
|
* a rounding error when a computation results in a mantissa of
|
|
|
|
|
* Number::kMaxRep that needs to be rounded up, but rounds down
|
|
|
|
|
* instead. It will maintain consistent behavior until the fixCleanup3_2_0
|
|
|
|
|
* amendment is enabled.
|
|
|
|
|
*
|
|
|
|
|
* 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
|
|
|
|
|
* should be left in place, but the "Small" scale option should be removed. This
|
|
|
|
|
* will allow for future expansion beyond 64-bits if it is ever needed.
|
|
|
|
|
*/
|
|
|
|
|
struct MantissaRange
|
|
|
|
|
struct MantissaRange final
|
|
|
|
|
{
|
|
|
|
|
using rep = std::uint64_t;
|
|
|
|
|
enum class MantissaScale { Small, Large };
|
|
|
|
|
enum class MantissaScale {
|
|
|
|
|
Small,
|
|
|
|
|
// LargeLegacy can be removed when fixCleanup3_2_0 is retired
|
|
|
|
|
LargeLegacy,
|
|
|
|
|
Large,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// This entire enum can be removed when fixCleanup3_2_0 is retired
|
|
|
|
|
enum class CuspRoundingFix : bool {
|
|
|
|
|
Disabled = false,
|
|
|
|
|
Enabled = true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
explicit constexpr MantissaRange(MantissaScale scale)
|
|
|
|
|
: min(getMin(scale)), log(logTen(min).value_or(-1)), scale(scale)
|
|
|
|
|
: min(getMin(scale))
|
|
|
|
|
, cuspRoundingFixEnabled(isCuspFixEnabled(scale))
|
|
|
|
|
, log(logTen(min).value_or(-1))
|
|
|
|
|
, scale(scale)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rep min;
|
|
|
|
|
rep max{(min * 10) - 1};
|
|
|
|
|
CuspRoundingFix cuspRoundingFixEnabled;
|
|
|
|
|
int log;
|
|
|
|
|
MantissaScale scale;
|
|
|
|
|
|
|
|
|
|
static MantissaRange const&
|
|
|
|
|
getMantissaRange(MantissaScale scale);
|
|
|
|
|
|
|
|
|
|
static std::set<MantissaScale> const&
|
|
|
|
|
getAllScales();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
static constexpr rep
|
|
|
|
|
getMin(MantissaScale scale)
|
|
|
|
|
@@ -90,15 +117,35 @@ private:
|
|
|
|
|
{
|
|
|
|
|
case MantissaScale::Small:
|
|
|
|
|
return 1'000'000'000'000'000ULL;
|
|
|
|
|
case MantissaScale::LargeLegacy:
|
|
|
|
|
case MantissaScale::Large:
|
|
|
|
|
return 1'000'000'000'000'000'000ULL;
|
|
|
|
|
default:
|
|
|
|
|
// Since this can never be called outside a non-constexpr
|
|
|
|
|
// context, this throw assures that the build fails if an
|
|
|
|
|
// If called in a constexpr context, this throw assures that the build fails if an
|
|
|
|
|
// invalid scale is used.
|
|
|
|
|
throw std::runtime_error("Unknown mantissa scale");
|
|
|
|
|
throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static constexpr CuspRoundingFix
|
|
|
|
|
isCuspFixEnabled(MantissaScale scale)
|
|
|
|
|
{
|
|
|
|
|
switch (scale)
|
|
|
|
|
{
|
|
|
|
|
case MantissaScale::Small:
|
|
|
|
|
case MantissaScale::LargeLegacy:
|
|
|
|
|
return CuspRoundingFix::Disabled;
|
|
|
|
|
case MantissaScale::Large:
|
|
|
|
|
return CuspRoundingFix::Enabled;
|
|
|
|
|
default:
|
|
|
|
|
// If called in a constexpr context, this throw assures that the build fails if an
|
|
|
|
|
// invalid scale is used.
|
|
|
|
|
throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::unordered_map<MantissaScale, MantissaRange> const&
|
|
|
|
|
getRanges();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Like std::integral, but only 64-bit integral types.
|
|
|
|
|
@@ -203,7 +250,7 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
|
|
|
|
|
* amendments are enabled to determine which result to expect.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
class Number
|
|
|
|
|
class Number final
|
|
|
|
|
{
|
|
|
|
|
using rep = std::int64_t;
|
|
|
|
|
using internalrep = MantissaRange::rep;
|
|
|
|
|
@@ -424,49 +471,29 @@ public:
|
|
|
|
|
return kRange.get().log;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// oneSmall is needed because the ranges are private
|
|
|
|
|
static constexpr Number
|
|
|
|
|
oneSmall();
|
|
|
|
|
/// oneLarge is needed because the ranges are private
|
|
|
|
|
static constexpr Number
|
|
|
|
|
oneLarge();
|
|
|
|
|
|
|
|
|
|
// And one is needed because it needs to choose between oneSmall and
|
|
|
|
|
// oneLarge based on the current range
|
|
|
|
|
static Number
|
|
|
|
|
one();
|
|
|
|
|
|
|
|
|
|
template <Integral64 T>
|
|
|
|
|
template <
|
|
|
|
|
auto MinMantissa,
|
|
|
|
|
auto MaxMantissa,
|
|
|
|
|
Integral64 T = std::decay_t<decltype(MinMantissa)>,
|
|
|
|
|
Integral64 TMax = std::decay_t<decltype(MaxMantissa)>>
|
|
|
|
|
[[nodiscard]]
|
|
|
|
|
std::pair<T, int>
|
|
|
|
|
normalizeToRange(T minMantissa, T maxMantissa) const;
|
|
|
|
|
normalizeToRange() const;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
static thread_local RoundingMode mode;
|
|
|
|
|
// The available ranges for mantissa
|
|
|
|
|
|
|
|
|
|
static constexpr MantissaRange kSmallRange{MantissaRange::MantissaScale::Small};
|
|
|
|
|
static_assert(isPowerOfTen(kSmallRange.min));
|
|
|
|
|
static_assert(kSmallRange.min == 1'000'000'000'000'000LL);
|
|
|
|
|
static_assert(kSmallRange.max == 9'999'999'999'999'999LL);
|
|
|
|
|
static_assert(kSmallRange.log == 15);
|
|
|
|
|
static_assert(kSmallRange.min < kMaxRep);
|
|
|
|
|
static_assert(kSmallRange.max < kMaxRep);
|
|
|
|
|
static constexpr MantissaRange kLargeRange{MantissaRange::MantissaScale::Large};
|
|
|
|
|
static_assert(isPowerOfTen(kLargeRange.min));
|
|
|
|
|
static_assert(kLargeRange.min == 1'000'000'000'000'000'000ULL);
|
|
|
|
|
static_assert(kLargeRange.max == internalrep(9'999'999'999'999'999'999ULL));
|
|
|
|
|
static_assert(kLargeRange.log == 18);
|
|
|
|
|
static_assert(kLargeRange.min < kMaxRep);
|
|
|
|
|
static_assert(kLargeRange.max > kMaxRep);
|
|
|
|
|
|
|
|
|
|
// The range for the mantissa when normalized.
|
|
|
|
|
// Use reference_wrapper to avoid making copies, and prevent accidentally
|
|
|
|
|
// changing the values inside the range.
|
|
|
|
|
static thread_local std::reference_wrapper<MantissaRange const> kRange;
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
normalize();
|
|
|
|
|
normalize(MantissaRange const& range);
|
|
|
|
|
|
|
|
|
|
/** Normalize Number components to an arbitrary range.
|
|
|
|
|
*
|
|
|
|
|
@@ -481,7 +508,8 @@ private:
|
|
|
|
|
T& mantissa,
|
|
|
|
|
int& exponent,
|
|
|
|
|
internalrep const& minMantissa,
|
|
|
|
|
internalrep const& maxMantissa);
|
|
|
|
|
internalrep const& maxMantissa,
|
|
|
|
|
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
|
|
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
|
friend void
|
|
|
|
|
@@ -490,7 +518,8 @@ private:
|
|
|
|
|
T& mantissa,
|
|
|
|
|
int& exponent,
|
|
|
|
|
MantissaRange::rep const& minMantissa,
|
|
|
|
|
MantissaRange::rep const& maxMantissa);
|
|
|
|
|
MantissaRange::rep const& maxMantissa,
|
|
|
|
|
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
|
|
|
|
|
|
|
|
|
|
[[nodiscard]] bool
|
|
|
|
|
isnormal() const noexcept;
|
|
|
|
|
@@ -526,7 +555,7 @@ static constexpr Number kNumZero{};
|
|
|
|
|
inline Number::Number(bool negative, internalrep mantissa, int exponent, Normalized)
|
|
|
|
|
: Number(negative, mantissa, exponent, Unchecked{})
|
|
|
|
|
{
|
|
|
|
|
normalize();
|
|
|
|
|
normalize(kRange);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline Number::Number(internalrep mantissa, int exponent, Normalized)
|
|
|
|
|
@@ -696,10 +725,19 @@ Number::isnormal() const noexcept
|
|
|
|
|
kMinExponent <= exponent_ && exponent_ <= kMaxExponent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <Integral64 T>
|
|
|
|
|
template <auto MinMantissa, auto MaxMantissa, Integral64 T, Integral64 TMax>
|
|
|
|
|
std::pair<T, int>
|
|
|
|
|
Number::normalizeToRange(T minMantissa, T maxMantissa) const
|
|
|
|
|
Number::normalizeToRange() const
|
|
|
|
|
{
|
|
|
|
|
static_assert(std::is_same_v<T, std::uint64_t> || std::is_same_v<T, std::int64_t>);
|
|
|
|
|
static_assert(std::is_same_v<T, TMax>);
|
|
|
|
|
auto constexpr kMIN = static_cast<T>(MinMantissa);
|
|
|
|
|
auto constexpr kMAX = static_cast<T>(MaxMantissa);
|
|
|
|
|
static_assert(kMIN > 0);
|
|
|
|
|
static_assert(kMIN % 10 == 0);
|
|
|
|
|
static_assert(kMAX % 10 == 9);
|
|
|
|
|
static_assert((kMAX + 1) / 10 == kMIN);
|
|
|
|
|
|
|
|
|
|
bool negative = negative_;
|
|
|
|
|
internalrep mantissa = mantissa_;
|
|
|
|
|
int exponent = exponent_;
|
|
|
|
|
@@ -711,7 +749,10 @@ Number::normalizeToRange(T minMantissa, T maxMantissa) const
|
|
|
|
|
"xrpl::Number::normalizeToRange",
|
|
|
|
|
"Number is non-negative for unsigned range.");
|
|
|
|
|
}
|
|
|
|
|
Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa);
|
|
|
|
|
// Don't need to worry about the cuspRounding fix because rounding up will never take the
|
|
|
|
|
// mantissa over maxMantissa with a ones digit value other than 0. 0 can safely be truncated.
|
|
|
|
|
Number::normalize(
|
|
|
|
|
negative, mantissa, exponent, kMIN, kMAX, MantissaRange::CuspRoundingFix::Disabled);
|
|
|
|
|
|
|
|
|
|
auto const sign = negative ? -1 : 1;
|
|
|
|
|
return std::make_pair(static_cast<T>(sign * mantissa), exponent);
|
|
|
|
|
@@ -763,6 +804,8 @@ to_string(MantissaRange::MantissaScale const& scale)
|
|
|
|
|
{
|
|
|
|
|
case MantissaRange::MantissaScale::Small:
|
|
|
|
|
return "small";
|
|
|
|
|
case MantissaRange::MantissaScale::LargeLegacy:
|
|
|
|
|
return "largeLegacy";
|
|
|
|
|
case MantissaRange::MantissaScale::Large:
|
|
|
|
|
return "large";
|
|
|
|
|
default:
|
|
|
|
|
|