Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 75 additions & 35 deletions include/xrpl/basics/Number.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <limits>
#include <optional>
#include <ostream>
#include <set>
#include <string>

namespace xrpl {
Expand Down Expand Up @@ -70,18 +71,39 @@ isPowerOfTen(T value)
struct MantissaRange
{
using rep = std::uint64_t;
enum class mantissa_scale { small, large };
enum class mantissa_scale {
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 cusp_rounding_fix : bool {
disabled = false,
enabled = true,
};

explicit constexpr MantissaRange(mantissa_scale 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};
cusp_rounding_fix cuspRoundingFixEnabled;
int log;
mantissa_scale scale;

static MantissaRange const&
getMantissaRange(mantissa_scale scale);

static std::set<mantissa_scale> const&
getAllScales();

private:
static constexpr rep
getMin(mantissa_scale scale_)
Expand All @@ -90,6 +112,7 @@ struct MantissaRange
{
case mantissa_scale::small:
return 1'000'000'000'000'000ULL;
case mantissa_scale::largeLegacy:
case mantissa_scale::large:
return 1'000'000'000'000'000'000ULL;
default:
Expand All @@ -99,6 +122,27 @@ struct MantissaRange
throw std::runtime_error("Unknown mantissa scale");
}
}

static constexpr cusp_rounding_fix
isCuspFixEnabled(mantissa_scale scale_)
{
switch (scale_)
{
case mantissa_scale::small:
case mantissa_scale::largeLegacy:
return cusp_rounding_fix::disabled;
case mantissa_scale::large:
return cusp_rounding_fix::enabled;
default:
// Since this can never be called outside a non-constexpr
// context, this throw assures that the build fails if an
// invalid scale is used.
throw std::runtime_error("Unknown mantissa scale");
}
}

static std::unordered_map<mantissa_scale, MantissaRange> const&
getRanges();
};

// Like std::integral, but only 64-bit integral types.
Expand Down Expand Up @@ -422,49 +466,29 @@ class Number
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();

template <Integral64 T>
template <
auto minMantissa,
auto maxMantissa,
Integral64 T = std::decay_t<decltype(minMantissa)>,
Integral64 TMax = std::decay_t<decltype(minMantissa)>>
[[nodiscard]]
std::pair<T, int>
normalizeToRange(T minMantissa, T maxMantissa) const;
normalizeToRange() const;

private:
static thread_local rounding_mode mode_;
// The available ranges for mantissa

constexpr static MantissaRange smallRange{MantissaRange::mantissa_scale::small};
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.log == 15);
static_assert(smallRange.min < maxRep);
static_assert(smallRange.max < maxRep);
constexpr static MantissaRange largeRange{MantissaRange::mantissa_scale::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(largeRange.log == 18);
static_assert(largeRange.min < maxRep);
static_assert(largeRange.max > maxRep);

// 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> range_;

void
normalize();
normalize(MantissaRange const& range);

/** Normalize Number components to an arbitrary range.
*
Expand All @@ -479,7 +503,8 @@ class Number
T& mantissa,
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa);
internalrep const& maxMantissa,
MantissaRange::cusp_rounding_fix cuspRoundingFixEnabled);

template <class T>
friend void
Expand All @@ -488,7 +513,8 @@ class Number
T& mantissa_,
int& exponent_,
MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa);
MantissaRange::rep const& maxMantissa,
MantissaRange::cusp_rounding_fix cuspRoundingFixEnabled);

[[nodiscard]] bool
isnormal() const noexcept;
Expand Down Expand Up @@ -524,7 +550,7 @@ constexpr static Number numZero{};
inline Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
: Number(negative, mantissa, exponent, unchecked{})
{
normalize();
normalize(range_);
}

inline Number::Number(internalrep mantissa, int exponent, normalized)
Expand Down Expand Up @@ -694,10 +720,19 @@ Number::isnormal() const noexcept
minExponent <= exponent_ && exponent_ <= maxExponent);
}

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 min = static_cast<T>(minMantissa);
auto constexpr max = static_cast<T>(maxMantissa);
static_assert(min > 0);
static_assert(min % 10 == 0);
static_assert(max % 10 == 9);
static_assert((max + 1) / 10 == min);

bool negative = negative_;
internalrep mantissa = mantissa_;
int exponent = exponent_;
Expand All @@ -709,7 +744,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, min, max, MantissaRange::cusp_rounding_fix::disabled);

auto const sign = negative ? -1 : 1;
return std::make_pair(static_cast<T>(sign * mantissa), exponent);
Expand Down Expand Up @@ -761,6 +799,8 @@ to_string(MantissaRange::mantissa_scale const& scale)
{
case MantissaRange::mantissa_scale::small:
return "small";
case MantissaRange::mantissa_scale::largeLegacy:
return "largeLegacy";
case MantissaRange::mantissa_scale::large:
return "large";
default:
Expand Down
2 changes: 1 addition & 1 deletion include/xrpl/protocol/STAmount.h
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ STAmount::fromNumber(A const& a, Number const& number)
return STAmount{asset, intValue, 0, negative};
}

auto const [mantissa, exponent] = working.normalizeToRange(cMinValue, cMaxValue);
auto const [mantissa, exponent] = working.normalizeToRange<cMinValue, cMaxValue>();

return STAmount{asset, mantissa, exponent, negative};
}
Expand Down
Loading
Loading