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 #ifndef XRPL_BASICS_NUMBER_H_INCLUDED
#define XRPL_BASICS_NUMBER_H_INCLUDED #define XRPL_BASICS_NUMBER_H_INCLUDED
#ifdef _MSC_VER
#include <boost/multiprecision/cpp_int.hpp>
#endif
#include <cstdint> #include <cstdint>
#include <limits> #include <limits>
#include <optional>
#include <ostream> #include <ostream>
#include <string> #include <string>
@@ -13,21 +18,66 @@ class Number;
std::string std::string
to_string(Number const& amount); 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 class Number
{ {
using uint128_t = numberuint128;
using int128_t = numberint128;
using rep = std::int64_t; using rep = std::int64_t;
rep mantissa_{0}; using internalrep = MantissaRange::rep;
internalrep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()}; int exponent_{std::numeric_limits<int>::lowest()};
public: 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 // The range for the exponent when normalized
constexpr static int minExponent = -32768; constexpr static int minExponent = -32768;
constexpr static int maxExponent = 32768; constexpr static int maxExponent = 32768;
// May need to make unchecked private
struct unchecked struct unchecked
{ {
explicit unchecked() = default; explicit unchecked() = default;
@@ -37,9 +87,12 @@ public:
Number(rep mantissa); Number(rep mantissa);
explicit Number(rep mantissa, int exponent); 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; mantissa() const noexcept;
constexpr int constexpr int
exponent() const noexcept; exponent() const noexcept;
@@ -68,11 +121,11 @@ public:
operator/=(Number const& x); operator/=(Number const& x);
static constexpr Number static constexpr Number
min() noexcept; min(MantissaRange const& range) noexcept;
static constexpr Number static constexpr Number
max() noexcept; max(MantissaRange const& range) noexcept;
static constexpr Number static constexpr Number
lowest() noexcept; lowest(MantissaRange const& range) noexcept;
/** Conversions to Number are implicit and conversions away from Number /** Conversions to Number are implicit and conversions away from Number
* are explicit. This design encourages and facilitates the use of Number * are explicit. This design encourages and facilitates the use of Number
@@ -181,18 +234,86 @@ public:
static rounding_mode static rounding_mode
setround(rounding_mode 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: private:
static thread_local rounding_mode mode_; 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 void
normalize(); normalize();
static void
normalize(
internalrep& mantissa,
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa);
constexpr bool 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; 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} : mantissa_{mantissa}, exponent_{exponent}
{ {
} }
@@ -203,11 +324,17 @@ inline Number::Number(rep mantissa, int exponent)
normalize(); normalize();
} }
inline Number::Number(internalrep mantissa, int exponent)
: mantissa_{mantissa}, exponent_{exponent}
{
normalize();
}
inline Number::Number(rep mantissa) : Number{mantissa, 0} inline Number::Number(rep mantissa) : Number{mantissa, 0}
{ {
} }
inline constexpr Number::rep inline constexpr Number::internalrep
Number::mantissa() const noexcept Number::mantissa() const noexcept
{ {
return mantissa_; return mantissa_;
@@ -302,31 +429,42 @@ operator/(Number const& x, Number const& y)
} }
inline constexpr Number 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 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 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 inline constexpr bool
Number::isnormal() const noexcept Number::isnormal(MantissaRange const& range) const noexcept
{ {
auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_; 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; 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 inline constexpr Number
abs(Number x) noexcept abs(Number x) noexcept
{ {

View File

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

View File

@@ -84,6 +84,12 @@ public:
return holds<Issue>() && get<Issue>().native(); return holds<Issue>() && get<Issue>().native();
} }
bool
integral() const
{
return !holds<Issue>() || get<Issue>().native();
}
friend constexpr bool friend constexpr bool
operator==(Asset const& lhs, Asset const& rhs); 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 boost::additive<IOUAmount>
{ {
private: private:
std::int64_t mantissa_; using mantissa_type = std::int64_t;
int exponent_; using exponent_type = int;
mantissa_type mantissa_;
exponent_type exponent_;
/** Adjusts the mantissa and exponent to the proper range. /** Adjusts the mantissa and exponent to the proper range.
@@ -38,11 +40,19 @@ private:
void void
normalize(); 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: public:
IOUAmount() = default; IOUAmount() = default;
explicit IOUAmount(Number const& other); explicit IOUAmount(Number const& other);
IOUAmount(beast::Zero); IOUAmount(beast::Zero);
IOUAmount(std::int64_t mantissa, int exponent); IOUAmount(mantissa_type mantissa, exponent_type exponent);
IOUAmount& operator=(beast::Zero); IOUAmount& operator=(beast::Zero);
@@ -71,10 +81,10 @@ public:
int int
signum() const noexcept; signum() const noexcept;
int exponent_type
exponent() const noexcept; exponent() const noexcept;
std::int64_t mantissa_type
mantissa() const noexcept; mantissa() const noexcept;
static IOUAmount static IOUAmount
@@ -92,7 +102,7 @@ inline IOUAmount::IOUAmount(beast::Zero)
*this = 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) : mantissa_(mantissa), exponent_(exponent)
{ {
normalize(); normalize();
@@ -149,13 +159,13 @@ IOUAmount::signum() const noexcept
return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0); return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0);
} }
inline int inline IOUAmount::exponent_type
IOUAmount::exponent() const noexcept IOUAmount::exponent() const noexcept
{ {
return exponent_; return exponent_;
} }
inline std::int64_t inline IOUAmount::mantissa_type
IOUAmount::mantissa() const noexcept IOUAmount::mantissa() const noexcept
{ {
return mantissa_; return mantissa_;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -50,8 +50,11 @@ STNumber::add(Serializer& s) const
XRPL_ASSERT( XRPL_ASSERT(
getFName().fieldType == getSType(), getFName().fieldType == getSType(),
"ripple::STNumber::add : field type match"); "ripple::STNumber::add : field type match");
s.add64(value_.mantissa()); constexpr std::int64_t min = 100'000'000'000'000'000LL;
s.add32(value_.exponent()); constexpr std::int64_t max = min * 10 - 1;
auto const [mantissa, exponent] = value_.normalizeToRange(min, max);
s.add64(mantissa);
s.add32(exponent);
} }
Number const& Number const&

View File

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

View File

@@ -2,6 +2,7 @@
#include <xrpl/beast/unit_test.h> #include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/IOUAmount.h> #include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/STAmount.h> #include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/SystemParameters.h>
#include <sstream> #include <sstream>
#include <tuple> #include <tuple>
@@ -685,19 +686,19 @@ public:
Issue const issue; Issue const issue;
Number const n{7'518'783'80596, -5}; Number const n{7'518'783'80596, -5};
saveNumberRoundMode const save{Number::setround(Number::to_nearest)}; 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}); BEAST_EXPECT(res2 == STAmount{7518784});
Number::setround(Number::towards_zero); Number::setround(Number::towards_zero);
res2 = STAmount{issue, n.mantissa(), n.exponent()}; res2 = STAmount{issue, n};
BEAST_EXPECT(res2 == STAmount{7518783}); BEAST_EXPECT(res2 == STAmount{7518783});
Number::setround(Number::downward); Number::setround(Number::downward);
res2 = STAmount{issue, n.mantissa(), n.exponent()}; res2 = STAmount{issue, n};
BEAST_EXPECT(res2 == STAmount{7518783}); BEAST_EXPECT(res2 == STAmount{7518783});
Number::setround(Number::upward); Number::setround(Number::upward);
res2 = STAmount{issue, n.mantissa(), n.exponent()}; res2 = STAmount{issue, n};
BEAST_EXPECT(res2 == STAmount{7518784}); BEAST_EXPECT(res2 == STAmount{7518784});
} }
@@ -725,6 +726,33 @@ public:
BEAST_EXPECT(Number(-100, -30000).truncate() == Number(0, 0)); 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 void
run() override run() override
{ {
@@ -746,6 +774,7 @@ public:
test_inc_dec(); test_inc_dec();
test_toSTAmount(); test_toSTAmount();
test_truncate(); test_truncate();
testInt64();
} }
}; };

View File

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