Step 1: Convert Number to use 128-bit numbers internally

- Update the conversion points between Number and *Amount & STNumber.
- Tests probably don't pass.
This commit is contained in:
Ed Hennis
2025-11-12 00:26:13 -05:00
parent 33309480d4
commit d030fdaa2b
18 changed files with 419 additions and 88 deletions

View File

@@ -1,8 +1,13 @@
#ifndef XRPL_BASICS_NUMBER_H_INCLUDED
#define XRPL_BASICS_NUMBER_H_INCLUDED
#ifdef _MSC_VER
#include <boost/multiprecision/cpp_int.hpp>
#endif
#include <cstdint>
#include <limits>
#include <optional>
#include <ostream>
#include <string>
@@ -13,21 +18,66 @@ class Number;
std::string
to_string(Number const& amount);
template <typename T>
constexpr std::optional<int>
logTen(T value)
{
int power = 0;
while (value >= 10 && value % 10 == 0)
{
value /= 10;
++power;
}
if (value == 1)
return power;
return std::nullopt;
}
template <typename T>
constexpr bool
isPowerOfTen(T value)
{
return logTen(value).has_value();
}
#ifdef _MSC_VER
using numberuint128 = boost::multiprecision::uint128_t;
using numberint128 = boost::multiprecision::int128_t;
#else // !defined(_MSC_VER)
using numberuint128 = __uint128_t;
using numberint128 = __int128_t;
#endif // !defined(_MSC_VER)
struct MantissaRange
{
using rep = numberint128;
explicit constexpr MantissaRange(rep min_)
: min(min_), max(min_ * 10 - 1), power(logTen(min).value_or(-1))
{
}
rep min;
rep max;
int power;
};
class Number
{
using uint128_t = numberuint128;
using int128_t = numberint128;
using rep = std::int64_t;
rep mantissa_{0};
using internalrep = MantissaRange::rep;
internalrep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()};
public:
// The range for the mantissa when normalized
constexpr static std::int64_t minMantissa = 1'000'000'000'000'000LL;
constexpr static std::int64_t maxMantissa = 9'999'999'999'999'999LL;
// The range for the exponent when normalized
constexpr static int minExponent = -32768;
constexpr static int maxExponent = 32768;
// May need to make unchecked private
struct unchecked
{
explicit unchecked() = default;
@@ -37,9 +87,12 @@ public:
Number(rep mantissa);
explicit Number(rep mantissa, int exponent);
explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept;
explicit constexpr Number(
internalrep mantissa,
int exponent,
unchecked) noexcept;
constexpr rep
constexpr internalrep
mantissa() const noexcept;
constexpr int
exponent() const noexcept;
@@ -68,11 +121,11 @@ public:
operator/=(Number const& x);
static constexpr Number
min() noexcept;
min(MantissaRange const& range) noexcept;
static constexpr Number
max() noexcept;
max(MantissaRange const& range) noexcept;
static constexpr Number
lowest() noexcept;
lowest(MantissaRange const& range) noexcept;
/** Conversions to Number are implicit and conversions away from Number
* are explicit. This design encourages and facilitates the use of Number
@@ -181,18 +234,86 @@ public:
static rounding_mode
setround(rounding_mode mode);
static void
setLargeMantissa(bool large);
inline static internalrep
minMantissa()
{
return range_.get().min;
}
inline static internalrep
maxMantissa()
{
return range_.get().max;
}
inline static internalrep
mantissaPower()
{
return range_.get().power;
}
constexpr static Number
oneSmall();
constexpr static Number
oneLarge();
static Number
one();
template <class T>
[[nodiscard]]
std::pair<T, int>
normalizeToRange(T minMantissa, T maxMantissa) const;
private:
static thread_local rounding_mode mode_;
// The available ranges for mantissa
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);
// 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.min < std::numeric_limits<std::int64_t>::max());
static_assert(largeRange.max > std::numeric_limits<std::int64_t>::max());
// 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();
static void
normalize(
internalrep& mantissa,
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa);
constexpr bool
isnormal() const noexcept;
isnormal(MantissaRange const& range) const noexcept;
explicit Number(internalrep mantissa, int exponent);
friend Number
root(Number f, unsigned d);
friend Number
root2(Number f);
class Guard;
};
inline constexpr Number::Number(rep mantissa, int exponent, unchecked) noexcept
inline constexpr Number::Number(
internalrep mantissa,
int exponent,
unchecked) noexcept
: mantissa_{mantissa}, exponent_{exponent}
{
}
@@ -203,11 +324,17 @@ inline Number::Number(rep mantissa, int exponent)
normalize();
}
inline Number::Number(internalrep mantissa, int exponent)
: mantissa_{mantissa}, exponent_{exponent}
{
normalize();
}
inline Number::Number(rep mantissa) : Number{mantissa, 0}
{
}
inline constexpr Number::rep
inline constexpr Number::internalrep
Number::mantissa() const noexcept
{
return mantissa_;
@@ -302,31 +429,42 @@ operator/(Number const& x, Number const& y)
}
inline constexpr Number
Number::min() noexcept
Number::min(MantissaRange const& range) noexcept
{
return Number{minMantissa, minExponent, unchecked{}};
return Number{range.min, minExponent, unchecked{}};
}
inline constexpr Number
Number::max() noexcept
Number::max(MantissaRange const& range) noexcept
{
return Number{maxMantissa, maxExponent, unchecked{}};
return Number{range.max, maxExponent, unchecked{}};
}
inline constexpr Number
Number::lowest() noexcept
Number::lowest(MantissaRange const& range) noexcept
{
return -Number{maxMantissa, maxExponent, unchecked{}};
return -Number{range.max, maxExponent, unchecked{}};
}
inline constexpr bool
Number::isnormal() const noexcept
Number::isnormal(MantissaRange const& range) const noexcept
{
auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_;
return minMantissa <= abs_m && abs_m <= maxMantissa &&
return range.min <= abs_m && abs_m <= range.max &&
minExponent <= exponent_ && exponent_ <= maxExponent;
}
template <class T>
std::pair<T, int>
Number::normalizeToRange(T minMantissa, T maxMantissa) const
{
internalrep mantissa = mantissa_;
int exponent = exponent_;
Number::normalize(mantissa, exponent, minMantissa, maxMantissa);
return std::make_pair(static_cast<T>(mantissa), exponent);
}
inline constexpr Number
abs(Number x) noexcept
{

View File

@@ -122,7 +122,7 @@ toAmount(
{
if (isXRP(issue))
return STAmount(issue, static_cast<std::int64_t>(n));
return STAmount(issue, n.mantissa(), n.exponent());
return STAmount(issue, n);
}
else
{

View File

@@ -84,6 +84,12 @@ public:
return holds<Issue>() && get<Issue>().native();
}
bool
integral() const
{
return !holds<Issue>() || get<Issue>().native();
}
friend constexpr bool
operator==(Asset const& lhs, Asset const& rhs);

View File

@@ -26,8 +26,10 @@ class IOUAmount : private boost::totally_ordered<IOUAmount>,
private boost::additive<IOUAmount>
{
private:
std::int64_t mantissa_;
int exponent_;
using mantissa_type = std::int64_t;
using exponent_type = int;
mantissa_type mantissa_;
exponent_type exponent_;
/** Adjusts the mantissa and exponent to the proper range.
@@ -38,11 +40,19 @@ private:
void
normalize();
IOUAmount(std::pair<mantissa_type, exponent_type> parts)
: IOUAmount(parts.first, parts.second)
{
}
static std::pair<mantissa_type, exponent_type>
scaleNumber(Number const& number);
public:
IOUAmount() = default;
explicit IOUAmount(Number const& other);
IOUAmount(beast::Zero);
IOUAmount(std::int64_t mantissa, int exponent);
IOUAmount(mantissa_type mantissa, exponent_type exponent);
IOUAmount& operator=(beast::Zero);
@@ -71,10 +81,10 @@ public:
int
signum() const noexcept;
int
exponent_type
exponent() const noexcept;
std::int64_t
mantissa_type
mantissa() const noexcept;
static IOUAmount
@@ -92,7 +102,7 @@ inline IOUAmount::IOUAmount(beast::Zero)
*this = beast::zero;
}
inline IOUAmount::IOUAmount(std::int64_t mantissa, int exponent)
inline IOUAmount::IOUAmount(mantissa_type mantissa, exponent_type exponent)
: mantissa_(mantissa), exponent_(exponent)
{
normalize();
@@ -149,13 +159,13 @@ IOUAmount::signum() const noexcept
return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0);
}
inline int
inline IOUAmount::exponent_type
IOUAmount::exponent() const noexcept
{
return exponent_;
}
inline std::int64_t
inline IOUAmount::mantissa_type
IOUAmount::mantissa() const noexcept
{
return mantissa_;

View File

@@ -37,6 +37,9 @@ public:
bool
native() const;
bool
integral() const;
friend constexpr std::weak_ordering
operator<=>(Issue const& lhs, Issue const& rhs);
};

View File

@@ -46,6 +46,12 @@ public:
{
return false;
}
bool
integral() const
{
return true;
}
};
constexpr bool

View File

@@ -47,9 +47,11 @@ public:
static int const cMaxOffset = 80;
// Maximum native value supported by the code
static std::uint64_t const cMinValue = 1000000000000000ull;
static std::uint64_t const cMaxValue = 9999999999999999ull;
static std::uint64_t const cMaxNative = 9000000000000000000ull;
constexpr static std::uint64_t cMinValue = 1'000'000'000'000'000ull;
static_assert(isPowerOfTen(cMinValue));
constexpr static std::uint64_t cMaxValue = cMinValue * 10 - 1;
static_assert(cMaxValue == 9'999'999'999'999'999ull);
static std::uint64_t const cMaxNative = 9'000'000'000'000'000'000ull;
// Max native value on network.
static std::uint64_t const cMaxNativeN = 100000000000000000ull;
@@ -136,7 +138,7 @@ public:
template <AssetType A>
STAmount(A const& asset, Number const& number)
: STAmount(asset, number.mantissa(), number.exponent())
: STAmount(asset, scaleNumber(asset, number))
{
}
@@ -155,6 +157,9 @@ public:
int
exponent() const noexcept;
bool
integral() const noexcept;
bool
native() const noexcept;
@@ -277,6 +282,22 @@ public:
mpt() const;
private:
template <AssetType A>
STAmount(
A const& asset,
std::tuple<mantissa_type, exponent_type, bool> parts)
: STAmount(
asset,
std::get<mantissa_type>(parts),
std::get<exponent_type>(parts),
std::get<bool>(parts))
{
}
template <AssetType A>
static std::tuple<mantissa_type, exponent_type, bool>
scaleNumber(A const& asset, Number const& number);
static std::unique_ptr<STAmount>
construct(SerialIter&, SField const& name);
@@ -435,6 +456,12 @@ STAmount::exponent() const noexcept
return mOffset;
}
inline bool
STAmount::integral() const noexcept
{
return mAsset.integral();
}
inline bool
STAmount::native() const noexcept
{
@@ -531,12 +558,29 @@ STAmount::operator=(XRPAmount const& amount)
return *this;
}
template <AssetType A>
inline std::tuple<STAmount::mantissa_type, STAmount::exponent_type, bool>
STAmount::scaleNumber(A const& asset, Number const& number)
{
bool const negative = number.mantissa() < 0;
if (asset.integral())
{
return std::make_tuple(std::int64_t(number), 0, negative);
}
else
{
Number const working{negative ? -number : number};
auto const [mantissa, exponent] =
working.normalizeToRange(cMinValue, cMaxValue);
return std::make_tuple(mantissa, exponent, negative);
}
}
inline STAmount&
STAmount::operator=(Number const& number)
{
mIsNegative = number.mantissa() < 0;
mValue = mIsNegative ? -number.mantissa() : number.mantissa();
mOffset = number.exponent();
std::tie(mValue, mOffset, mIsNegative) = scaleNumber(mAsset, number);
canonicalize();
return *this;
}
@@ -553,7 +597,7 @@ STAmount::clear()
{
// The -100 is used to allow 0 to sort less than a small positive values
// which have a negative exponent.
mOffset = native() ? 0 : -100;
mOffset = integral() ? 0 : -100;
mValue = 0;
mIsNegative = false;
}

View File

@@ -482,6 +482,8 @@ public:
value_type
operator*() const;
/// Do not use operator->() unless the field is required, or you've checked
/// that it's set.
T const*
operator->() const;
@@ -718,6 +720,8 @@ STObject::Proxy<T>::operator*() const -> value_type
return this->value();
}
/// Do not use operator->() unless the field is required, or you've checked that
/// it's set.
template <class T>
T const*
STObject::Proxy<T>::operator->() const

View File

@@ -23,6 +23,7 @@ systemName()
/** Number of drops in the genesis account. */
constexpr XRPAmount INITIAL_XRP{100'000'000'000 * DROPS_PER_XRP};
static_assert(INITIAL_XRP.drops() == 100'000'000'000'000'000);
/** Returns true if the amount does not exceed the initial XRP in existence. */
inline bool

View File

@@ -479,10 +479,10 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
{sfAccount, soeREQUIRED},
{sfData, soeOPTIONAL},
{sfAsset, soeREQUIRED},
{sfAssetsTotal, soeREQUIRED},
{sfAssetsAvailable, soeREQUIRED},
{sfAssetsTotal, soeDEFAULT},
{sfAssetsAvailable, soeDEFAULT},
{sfAssetsMaximum, soeDEFAULT},
{sfLossUnrealized, soeREQUIRED},
{sfLossUnrealized, soeDEFAULT},
{sfShareMPTID, soeREQUIRED},
{sfWithdrawalPolicy, soeREQUIRED},
{sfScale, soeDEFAULT},

View File

@@ -14,15 +14,14 @@
#ifdef _MSC_VER
#pragma message("Using boost::multiprecision::uint128_t")
#include <boost/multiprecision/cpp_int.hpp>
using uint128_t = boost::multiprecision::uint128_t;
#else // !defined(_MSC_VER)
using uint128_t = __uint128_t;
#endif // !defined(_MSC_VER)
#endif
namespace ripple {
thread_local Number::rounding_mode Number::mode_ = Number::to_nearest;
// TODO: Once the Rules switching is implemented, default to largeRange
thread_local std::reference_wrapper<MantissaRange const> Number::range_ =
smallRange; // largeRange;
Number::rounding_mode
Number::getround()
@@ -63,7 +62,7 @@ public:
// add a digit
void
push(unsigned d) noexcept;
push(numberuint128 d) noexcept;
// recover a digit
unsigned
@@ -95,8 +94,10 @@ Number::Guard::is_negative() const noexcept
}
inline void
Number::Guard::push(unsigned d) noexcept
Number::Guard::push(numberuint128 d128) noexcept
{
unsigned d = static_cast<unsigned>(d128);
xbit_ = xbit_ || (digits_ & 0x0000'0000'0000'000F) != 0;
digits_ >>= 4;
digits_ |= (d & 0x0000'0000'0000'000FULL) << 60;
@@ -153,14 +154,42 @@ Number::Guard::round() noexcept
// Number
constexpr Number one{1000000000000000, -15, Number::unchecked{}};
constexpr Number
Number::oneSmall()
{
return Number{
Number::smallRange.min, -Number::smallRange.power, Number::unchecked{}};
};
constexpr Number
Number::oneLarge()
{
return Number{
Number::largeRange.min, -Number::largeRange.power, Number::unchecked{}};
};
Number
Number::one()
{
if (&range_.get() == &smallRange)
return oneSmall();
XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_");
return oneLarge();
}
// Use the member names in this static function for now so the diff is cleaner
void
Number::normalize()
Number::normalize(
internalrep& mantissa_,
int& exponent_,
internalrep const& minMantissa,
internalrep const& maxMantissa)
{
if (mantissa_ == 0)
{
*this = Number{};
constexpr Number zero = Number{};
mantissa_ = zero.mantissa_;
exponent_ = zero.exponent_;
return;
}
bool const negative = (mantissa_ < 0);
@@ -186,7 +215,9 @@ Number::normalize()
mantissa_ = m;
if ((exponent_ < minExponent) || (mantissa_ < minMantissa))
{
*this = Number{};
constexpr Number zero = Number{};
mantissa_ = zero.mantissa_;
exponent_ = zero.exponent_;
return;
}
@@ -207,6 +238,13 @@ Number::normalize()
mantissa_ = -mantissa_;
}
void
Number::normalize()
{
normalize(
mantissa_, exponent_, Number::minMantissa(), Number::maxMantissa());
}
Number&
Number::operator+=(Number const& y)
{
@@ -223,7 +261,7 @@ Number::operator+=(Number const& y)
return *this;
}
XRPL_ASSERT(
isnormal() && y.isnormal(),
isnormal(Number::range_) && y.isnormal(Number::range_),
"ripple::Number::operator+=(Number) : is normal");
auto xm = mantissa();
auto xe = exponent();
@@ -266,6 +304,8 @@ Number::operator+=(Number const& y)
}
if (xn == yn)
{
auto const maxMantissa = Number::maxMantissa();
xm += ym;
if (xm > maxMantissa)
{
@@ -288,6 +328,8 @@ Number::operator+=(Number const& y)
}
else
{
auto const minMantissa = Number::minMantissa();
if (xm > ym)
{
xm = xm - ym;
@@ -332,7 +374,7 @@ Number::operator+=(Number const& y)
// Derived from Hacker's Delight Second Edition Chapter 10
// by Henry S. Warren, Jr.
static inline unsigned
divu10(uint128_t& u)
divu10(numberuint128& u)
{
// q = u * 0.75
auto q = (u >> 1) + (u >> 2);
@@ -364,7 +406,7 @@ Number::operator*=(Number const& y)
return *this;
}
XRPL_ASSERT(
isnormal() && y.isnormal(),
isnormal(Number::range_) && y.isnormal(Number::range_),
"ripple::Number::operator*=(Number) : is normal");
auto xm = mantissa();
auto xe = exponent();
@@ -382,12 +424,15 @@ Number::operator*=(Number const& y)
ym = -ym;
yn = -1;
}
auto zm = uint128_t(xm) * uint128_t(ym);
auto zm = numberuint128(xm) * numberuint128(ym);
auto ze = xe + ye;
auto zn = xn * yn;
Guard g;
if (zn == -1)
g.set_negative();
auto const maxMantissa = Number::maxMantissa();
while (zm > maxMantissa)
{
// The following is optimization for:
@@ -420,7 +465,7 @@ Number::operator*=(Number const& y)
mantissa_ = xm * zn;
exponent_ = xe;
XRPL_ASSERT(
isnormal() || *this == Number{},
isnormal(Number::range_) || *this == Number{},
"ripple::Number::operator*=(Number) : result is normal");
return *this;
}
@@ -448,10 +493,13 @@ Number::operator/=(Number const& y)
dm = -dm;
dp = -1;
}
// Shift by 10^17 gives greatest precision while not overflowing uint128_t
// or the cast back to int64_t
uint128_t const f = 100'000'000'000'000'000;
mantissa_ = static_cast<std::int64_t>(uint128_t(nm) * f / uint128_t(dm));
// Shift by 10^17 gives greatest precision while not overflowing
// numberuint128 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;
static_assert(f == smallRange.min * 100);
static_assert(smallRange.power == 15);
mantissa_ = numberuint128(nm) * f / numberuint128(dm);
exponent_ = ne - de - 17;
mantissa_ *= np * dp;
normalize();
@@ -460,7 +508,7 @@ Number::operator/=(Number const& y)
Number::operator rep() const
{
rep drops = mantissa_;
internalrep drops = mantissa_;
int offset = exponent_;
Guard g;
if (drops != 0)
@@ -477,10 +525,12 @@ Number::operator rep() const
}
for (; offset > 0; --offset)
{
if (drops > std::numeric_limits<decltype(drops)>::max() / 10)
if (drops > std::numeric_limits<rep>::max() / 10)
throw std::overflow_error("Number::operator rep() overflow");
drops *= 10;
}
if (drops > std::numeric_limits<rep>::max() / 10)
throw std::overflow_error("Number::operator rep() overflow");
auto r = g.round();
if (r == 1 || (r == 0 && (drops & 1) == 1))
{
@@ -489,7 +539,7 @@ Number::operator rep() const
if (g.is_negative())
drops = -drops;
}
return drops;
return static_cast<rep>(drops);
}
std::string
@@ -503,9 +553,11 @@ to_string(Number const& amount)
auto mantissa = amount.mantissa();
// Use scientific notation for exponents that are too small or too large
if (((exponent != 0) && ((exponent < -25) || (exponent > -5))))
auto const rangePower = Number::mantissaPower();
if (((exponent != 0) &&
((exponent < -(rangePower + 10)) || (exponent > -(rangePower - 10)))))
{
std::string ret = std::to_string(mantissa);
std::string ret = to_string(mantissa);
ret.append(1, 'e');
ret.append(std::to_string(exponent));
return ret;
@@ -525,7 +577,7 @@ to_string(Number const& amount)
ptrdiff_t const pad_prefix = 27;
ptrdiff_t const pad_suffix = 23;
std::string const raw_value(std::to_string(mantissa));
std::string const raw_value(to_string(mantissa));
std::string val;
val.reserve(raw_value.length() + pad_prefix + pad_suffix);
@@ -594,7 +646,7 @@ Number
power(Number const& f, unsigned n)
{
if (n == 0)
return one;
return Number::one();
if (n == 1)
return f;
auto r = power(f, n / 2);
@@ -616,6 +668,8 @@ power(Number const& f, unsigned n)
Number
root(Number f, unsigned d)
{
auto const one = Number::one();
if (f == one || d == 1)
return f;
if (d == 0)
@@ -681,6 +735,8 @@ root(Number f, unsigned d)
Number
root2(Number f)
{
auto const one = Number::one();
if (f == one)
return f;
if (f < Number{})
@@ -721,6 +777,8 @@ root2(Number f)
Number
power(Number const& f, unsigned n, unsigned d)
{
auto const one = Number::one();
if (f == one)
return f;
auto g = std::gcd(n, d);

View File

@@ -40,12 +40,18 @@ setSTNumberSwitchover(bool v)
}
/* The range for the mantissa when normalized */
static std::int64_t constexpr minMantissa = 1000000000000000ull;
static std::int64_t constexpr maxMantissa = 9999999999999999ull;
static std::int64_t constexpr minMantissa = 1'000'000'000'000'000ull;
static std::int64_t constexpr maxMantissa = minMantissa * 10 - 1;
/* The range for the exponent when normalized */
static int constexpr minExponent = -96;
static int constexpr maxExponent = 80;
std::pair<IOUAmount::mantissa_type, IOUAmount::exponent_type>
IOUAmount::scaleNumber(Number const& number)
{
return number.normalizeToRange(minMantissa, maxMantissa);
}
IOUAmount
IOUAmount::minPositiveAmount()
{
@@ -64,8 +70,7 @@ IOUAmount::normalize()
if (getSTNumberSwitchover())
{
Number const v{mantissa_, exponent_};
mantissa_ = v.mantissa();
exponent_ = v.exponent();
std::tie(mantissa_, exponent_) = scaleNumber(v);
if (exponent_ > maxExponent)
Throw<std::overflow_error>("value overflow");
if (exponent_ < minExponent)
@@ -106,8 +111,7 @@ IOUAmount::normalize()
mantissa_ = -mantissa_;
}
IOUAmount::IOUAmount(Number const& other)
: mantissa_(other.mantissa()), exponent_(other.exponent())
IOUAmount::IOUAmount(Number const& other) : IOUAmount(scaleNumber(other))
{
if (exponent_ > maxExponent)
Throw<std::overflow_error>("value overflow");

View File

@@ -49,6 +49,12 @@ Issue::native() const
return *this == xrpIssue();
}
bool
Issue::integral() const
{
return native();
}
bool
isConsistent(Issue const& ac)
{

View File

@@ -1323,7 +1323,7 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset)
if (getSTNumberSwitchover())
{
auto const r = Number{v1} * Number{v2};
return STAmount{asset, r.mantissa(), r.exponent()};
return STAmount{asset, r};
}
std::uint64_t value1 = v1.mantissa();

View File

@@ -50,8 +50,11 @@ STNumber::add(Serializer& s) const
XRPL_ASSERT(
getFName().fieldType == getSType(),
"ripple::STNumber::add : field type match");
s.add64(value_.mantissa());
s.add32(value_.exponent());
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&

View File

@@ -1384,15 +1384,14 @@ private:
// equal asset deposit: unit test to exercise the rounding-down of
// LPTokens in the AMMHelpers.cpp: adjustLPTokens calculations
// The LPTokens need to have 16 significant digits and a fractional part
for (Number const deltaLPTokens :
for (Number const& deltaLPTokens :
{Number{UINT64_C(100000'0000000009), -10},
Number{UINT64_C(100000'0000000001), -10}})
{
testAMM([&](AMM& ammAlice, Env& env) {
// initial LPToken balance
IOUAmount const initLPToken = ammAlice.getLPTokensBalance();
IOUAmount const newLPTokens{
deltaLPTokens.mantissa(), deltaLPTokens.exponent()};
IOUAmount const newLPTokens{deltaLPTokens};
// carol performs a two-asset deposit
ammAlice.deposit(
@@ -1417,11 +1416,9 @@ private:
Number const deltaXRP = fr * 1e10;
Number const deltaUSD = fr * 1e4;
STAmount const depositUSD =
STAmount{USD, deltaUSD.mantissa(), deltaUSD.exponent()};
STAmount const depositUSD = STAmount{USD, deltaUSD};
STAmount const depositXRP =
STAmount{XRP, deltaXRP.mantissa(), deltaXRP.exponent()};
STAmount const depositXRP = STAmount{XRP, deltaXRP};
// initial LPTokens (1e7) + newLPTokens
BEAST_EXPECT(ammAlice.expectBalances(

View File

@@ -2,6 +2,7 @@
#include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/SystemParameters.h>
#include <sstream>
#include <tuple>
@@ -685,19 +686,19 @@ public:
Issue const issue;
Number const n{7'518'783'80596, -5};
saveNumberRoundMode const save{Number::setround(Number::to_nearest)};
auto res2 = STAmount{issue, n.mantissa(), n.exponent()};
auto res2 = STAmount{issue, n};
BEAST_EXPECT(res2 == STAmount{7518784});
Number::setround(Number::towards_zero);
res2 = STAmount{issue, n.mantissa(), n.exponent()};
res2 = STAmount{issue, n};
BEAST_EXPECT(res2 == STAmount{7518783});
Number::setround(Number::downward);
res2 = STAmount{issue, n.mantissa(), n.exponent()};
res2 = STAmount{issue, n};
BEAST_EXPECT(res2 == STAmount{7518783});
Number::setround(Number::upward);
res2 = STAmount{issue, n.mantissa(), n.exponent()};
res2 = STAmount{issue, n};
BEAST_EXPECT(res2 == STAmount{7518784});
}
@@ -725,6 +726,33 @@ public:
BEAST_EXPECT(Number(-100, -30000).truncate() == Number(0, 0));
}
void
testInt64()
{
testcase("std::int64_t");
// Control case
BEAST_EXPECT(Number::maxMantissa() > 10);
Number ten{10};
BEAST_EXPECT(ten.exponent() <= 0);
BEAST_EXPECT(
std::numeric_limits<std::int64_t>::max() > INITIAL_XRP.drops());
BEAST_EXPECT(Number::maxMantissa() > INITIAL_XRP.drops());
Number initalXrp{INITIAL_XRP};
BEAST_EXPECT(initalXrp.exponent() <= 0);
Number maxInt64{std::numeric_limits<std::int64_t>::max()};
BEAST_EXPECT(maxInt64.exponent() <= 0);
using namespace boost::multiprecision;
// maxint64 9,223,372,036,854,775,808
int128_t minMantissa{1'000'000'000'000'000'000LL};
int128_t maxMantissa{minMantissa * 10 - 1};
BEAST_EXPECT(minMantissa < std::numeric_limits<std::int64_t>::max());
BEAST_EXPECT(maxMantissa > std::numeric_limits<std::int64_t>::max());
}
void
run() override
{
@@ -746,6 +774,7 @@ public:
test_inc_dec();
test_toSTAmount();
test_truncate();
testInt64();
}
};

View File

@@ -192,6 +192,12 @@ public:
return to_json(asset_);
}
bool
integral() const
{
return asset_.integral();
}
template <std::integral T>
PrettyAmount
operator()(T v, Number::rounding_mode rounding = Number::getround()) const
@@ -242,6 +248,12 @@ struct XRP_t
return xrpIssue();
}
bool
integral() const
{
return true;
}
/** Returns an amount of XRP as PrettyAmount,
which is trivially convertable to STAmount
@@ -366,6 +378,11 @@ public:
{
return issue();
}
bool
integral() const
{
return issue().integral();
}
/** Implicit conversion to Issue or Asset.
@@ -456,6 +473,11 @@ public:
{
return mptIssue();
}
bool
integral() const
{
return true;
}
/** Implicit conversion to MPTIssue or asset.