mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 18:45:52 +00:00
Compare commits
23 Commits
ximinez/le
...
ximinez/le
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e45d847558 | ||
|
|
546bfa89d8 | ||
|
|
470c9c3936 | ||
|
|
9f50cc033e | ||
|
|
a5f43fb59b | ||
|
|
3451d15e12 | ||
|
|
595a5ee220 | ||
|
|
c9ad49faf3 | ||
|
|
857eaffa55 | ||
|
|
93109918ed | ||
|
|
fbcd4f33eb | ||
|
|
6f1fe5047b | ||
|
|
4cf22b50de | ||
|
|
606e3ec0b7 | ||
|
|
2eca3dca89 | ||
|
|
2881aade2c | ||
|
|
4abb6d9dfe | ||
|
|
7cd48a7713 | ||
|
|
d2d403da90 | ||
|
|
343824332c | ||
|
|
a32b5723e5 | ||
|
|
3048f55270 | ||
|
|
d030fdaa2b |
@@ -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,71 @@ class Number;
|
||||
std::string
|
||||
to_string(Number const& amount);
|
||||
|
||||
template <typename T>
|
||||
constexpr std::optional<int>
|
||||
logTen(T value)
|
||||
{
|
||||
int log = 0;
|
||||
while (value >= 10 && value % 10 == 0)
|
||||
{
|
||||
value /= 10;
|
||||
++log;
|
||||
}
|
||||
if (value == 1)
|
||||
return log;
|
||||
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 internalrep = numberint128;
|
||||
enum mantissa_scale { small, large };
|
||||
|
||||
explicit constexpr MantissaRange(mantissa_scale scale_, internalrep min_)
|
||||
: min(min_)
|
||||
, max(min_ * 10 - 1)
|
||||
, log(logTen(min).value_or(-1))
|
||||
, scale(scale_)
|
||||
{
|
||||
}
|
||||
|
||||
internalrep min;
|
||||
internalrep max;
|
||||
int log;
|
||||
mantissa_scale scale;
|
||||
};
|
||||
|
||||
class Number
|
||||
{
|
||||
using uint128_t = numberuint128;
|
||||
using int128_t = numberint128;
|
||||
|
||||
using rep = std::int64_t;
|
||||
rep mantissa_{0};
|
||||
using internalrep = MantissaRange::internalrep;
|
||||
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;
|
||||
@@ -36,10 +91,13 @@ public:
|
||||
explicit constexpr Number() = default;
|
||||
|
||||
Number(rep mantissa);
|
||||
explicit Number(rep mantissa, int exponent);
|
||||
explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept;
|
||||
explicit Number(internalrep mantissa, int exponent);
|
||||
explicit constexpr Number(
|
||||
internalrep mantissa,
|
||||
int exponent,
|
||||
unchecked) noexcept;
|
||||
|
||||
constexpr rep
|
||||
constexpr internalrep
|
||||
mantissa() const noexcept;
|
||||
constexpr int
|
||||
exponent() const noexcept;
|
||||
@@ -141,7 +199,7 @@ public:
|
||||
while (ret.exponent_ < 0 && ret.mantissa_ != 0)
|
||||
{
|
||||
ret.exponent_ += 1;
|
||||
ret.mantissa_ /= rep(10);
|
||||
ret.mantissa_ /= internalrep(10);
|
||||
}
|
||||
// We are guaranteed that normalize() will never throw an exception
|
||||
// because exponent is either negative or zero at this point.
|
||||
@@ -181,23 +239,96 @@ public:
|
||||
static rounding_mode
|
||||
setround(rounding_mode mode);
|
||||
|
||||
static MantissaRange::mantissa_scale
|
||||
getMantissaScale();
|
||||
static void
|
||||
setMantissaScale(MantissaRange::mantissa_scale scale);
|
||||
|
||||
inline static internalrep
|
||||
minMantissa()
|
||||
{
|
||||
return range_.get().min;
|
||||
}
|
||||
|
||||
inline static internalrep
|
||||
maxMantissa()
|
||||
{
|
||||
return range_.get().max;
|
||||
}
|
||||
|
||||
inline static int
|
||||
mantissaLog()
|
||||
{
|
||||
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 <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{
|
||||
MantissaRange::small,
|
||||
1'000'000'000'000'000LL};
|
||||
static_assert(isPowerOfTen(smallRange.min));
|
||||
static_assert(smallRange.max == 9'999'999'999'999'999LL);
|
||||
static_assert(smallRange.log == 15);
|
||||
// maxint64 9,223,372,036,854,775,808
|
||||
constexpr static MantissaRange largeRange{
|
||||
MantissaRange::large,
|
||||
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.log == 18);
|
||||
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;
|
||||
|
||||
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}
|
||||
{
|
||||
}
|
||||
|
||||
inline Number::Number(rep mantissa, int exponent)
|
||||
inline Number::Number(internalrep mantissa, int exponent)
|
||||
: mantissa_{mantissa}, exponent_{exponent}
|
||||
{
|
||||
normalize();
|
||||
@@ -207,7 +338,7 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
|
||||
{
|
||||
}
|
||||
|
||||
inline constexpr Number::rep
|
||||
inline constexpr Number::internalrep
|
||||
Number::mantissa() const noexcept
|
||||
{
|
||||
return mantissa_;
|
||||
@@ -236,7 +367,7 @@ Number::operator-() const noexcept
|
||||
inline Number&
|
||||
Number::operator++()
|
||||
{
|
||||
*this += Number{1000000000000000, -15, unchecked{}};
|
||||
*this += one();
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -251,7 +382,7 @@ Number::operator++(int)
|
||||
inline Number&
|
||||
Number::operator--()
|
||||
{
|
||||
*this -= Number{1000000000000000, -15, unchecked{}};
|
||||
*this -= one();
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -304,29 +435,41 @@ operator/(Number const& x, Number const& y)
|
||||
inline constexpr Number
|
||||
Number::min() noexcept
|
||||
{
|
||||
return Number{minMantissa, minExponent, unchecked{}};
|
||||
return Number{range_.get().min, minExponent, unchecked{}};
|
||||
}
|
||||
|
||||
inline constexpr Number
|
||||
Number::max() noexcept
|
||||
{
|
||||
return Number{maxMantissa, maxExponent, unchecked{}};
|
||||
return Number{range_.get().max, maxExponent, unchecked{}};
|
||||
}
|
||||
|
||||
inline constexpr Number
|
||||
Number::lowest() noexcept
|
||||
{
|
||||
return -Number{maxMantissa, maxExponent, unchecked{}};
|
||||
return -Number{range_.get().max, maxExponent, unchecked{}};
|
||||
}
|
||||
|
||||
inline constexpr bool
|
||||
Number::isnormal() const noexcept
|
||||
{
|
||||
MantissaRange const& range = range_;
|
||||
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
|
||||
{
|
||||
@@ -366,6 +509,20 @@ squelch(Number const& x, Number const& limit) noexcept
|
||||
return x;
|
||||
}
|
||||
|
||||
inline std::string
|
||||
to_string(MantissaRange::mantissa_scale const& scale)
|
||||
{
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::small:
|
||||
return "small";
|
||||
case MantissaRange::large:
|
||||
return "large";
|
||||
default:
|
||||
throw std::runtime_error("Bad scale");
|
||||
}
|
||||
}
|
||||
|
||||
class saveNumberRoundMode
|
||||
{
|
||||
Number::rounding_mode mode_;
|
||||
@@ -404,6 +561,33 @@ public:
|
||||
operator=(NumberRoundModeGuard const&) = delete;
|
||||
};
|
||||
|
||||
// Sets the new scale and restores the old scale when it leaves scope. Since
|
||||
// Number doesn't have that facility, we'll build it here.
|
||||
//
|
||||
// This class may only end up needed in tests
|
||||
class NumberMantissaScaleGuard
|
||||
{
|
||||
MantissaRange::mantissa_scale saved_;
|
||||
|
||||
public:
|
||||
explicit NumberMantissaScaleGuard(
|
||||
MantissaRange::mantissa_scale scale) noexcept
|
||||
: saved_{Number::getMantissaScale()}
|
||||
{
|
||||
Number::setMantissaScale(scale);
|
||||
}
|
||||
|
||||
~NumberMantissaScaleGuard()
|
||||
{
|
||||
Number::setMantissaScale(saved_);
|
||||
}
|
||||
|
||||
NumberMantissaScaleGuard(NumberMantissaScaleGuard const&) = delete;
|
||||
|
||||
NumberMantissaScaleGuard&
|
||||
operator=(NumberMantissaScaleGuard const&) = delete;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif // XRPL_BASICS_NUMBER_H_INCLUDED
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -84,6 +84,19 @@ public:
|
||||
return holds<Issue>() && get<Issue>().native();
|
||||
}
|
||||
|
||||
bool
|
||||
integral() const
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
return issue.native();
|
||||
if constexpr (std::is_same_v<TIss, MPTIssue>)
|
||||
return true;
|
||||
},
|
||||
issue_);
|
||||
}
|
||||
|
||||
friend constexpr bool
|
||||
operator==(Asset const& lhs, Asset const& rhs);
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -37,6 +37,9 @@ public:
|
||||
bool
|
||||
native() const;
|
||||
|
||||
bool
|
||||
integral() const;
|
||||
|
||||
friend constexpr std::weak_ordering
|
||||
operator<=>(Issue const& lhs, Issue const& rhs);
|
||||
};
|
||||
|
||||
@@ -46,6 +46,12 @@ public:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
integral() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr bool
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -340,10 +361,19 @@ STAmount::STAmount(
|
||||
, mIsNegative(negative)
|
||||
{
|
||||
// mValue is uint64, but needs to fit in the range of int64
|
||||
XRPL_ASSERT(
|
||||
mValue <= std::numeric_limits<std::int64_t>::max(),
|
||||
"ripple::STAmount::STAmount(SField, A, std::uint64_t, int, bool) : "
|
||||
"maximum mantissa input");
|
||||
if (Number::getMantissaScale() == MantissaRange::small)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
mValue <= std::numeric_limits<std::int64_t>::max(),
|
||||
"ripple::STAmount::STAmount(SField, A, std::uint64_t, int, bool) : "
|
||||
"maximum mantissa input");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (integral() && mValue > std::numeric_limits<std::int64_t>::max())
|
||||
throw std::overflow_error(
|
||||
"STAmount mantissa is too large " + std::to_string(mantissa));
|
||||
}
|
||||
canonicalize();
|
||||
}
|
||||
|
||||
@@ -435,6 +465,12 @@ STAmount::exponent() const noexcept
|
||||
return mOffset;
|
||||
}
|
||||
|
||||
inline bool
|
||||
STAmount::integral() const noexcept
|
||||
{
|
||||
return mAsset.integral();
|
||||
}
|
||||
|
||||
inline bool
|
||||
STAmount::native() const noexcept
|
||||
{
|
||||
@@ -531,12 +567,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;
|
||||
Number const working{negative ? -number : number};
|
||||
if (asset.integral())
|
||||
{
|
||||
return std::make_tuple(std::int64_t(working), 0, negative);
|
||||
}
|
||||
else
|
||||
{
|
||||
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 +606,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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include <xrpl/basics/Number.h>
|
||||
//
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -19,10 +21,23 @@ using uint128_t = boost::multiprecision::uint128_t;
|
||||
#else // !defined(_MSC_VER)
|
||||
using uint128_t = __uint128_t;
|
||||
#endif // !defined(_MSC_VER)
|
||||
static_assert(std::is_same_v<uint128_t, ripple::numberuint128>);
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct make_unsigned<ripple::numberint128>
|
||||
{
|
||||
using type = uint128_t;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace ripple {
|
||||
|
||||
thread_local Number::rounding_mode Number::mode_ = Number::to_nearest;
|
||||
thread_local std::reference_wrapper<MantissaRange const> Number::range_ =
|
||||
largeRange;
|
||||
|
||||
Number::rounding_mode
|
||||
Number::getround()
|
||||
@@ -36,6 +51,21 @@ Number::setround(rounding_mode mode)
|
||||
return std::exchange(mode_, mode);
|
||||
}
|
||||
|
||||
MantissaRange::mantissa_scale
|
||||
Number::getMantissaScale()
|
||||
{
|
||||
return range_.get().scale;
|
||||
}
|
||||
|
||||
void
|
||||
Number::setMantissaScale(MantissaRange::mantissa_scale scale)
|
||||
{
|
||||
// scale_ and range_ MUST stay in lockstep
|
||||
if (scale != MantissaRange::small && scale != MantissaRange::large)
|
||||
LogicError("Unknown mantissa scale");
|
||||
range_ = scale == MantissaRange::small ? smallRange : largeRange;
|
||||
}
|
||||
|
||||
// Guard
|
||||
|
||||
// The Guard class is used to tempoarily add extra digits of
|
||||
@@ -62,8 +92,9 @@ public:
|
||||
is_negative() const noexcept;
|
||||
|
||||
// add a digit
|
||||
template <class T>
|
||||
void
|
||||
push(unsigned d) noexcept;
|
||||
push(T d) noexcept;
|
||||
|
||||
// recover a digit
|
||||
unsigned
|
||||
@@ -74,6 +105,10 @@ public:
|
||||
// tie, round towards even.
|
||||
int
|
||||
round() noexcept;
|
||||
|
||||
private:
|
||||
void
|
||||
doPush(unsigned d) noexcept;
|
||||
};
|
||||
|
||||
inline void
|
||||
@@ -95,13 +130,20 @@ Number::Guard::is_negative() const noexcept
|
||||
}
|
||||
|
||||
inline void
|
||||
Number::Guard::push(unsigned d) noexcept
|
||||
Number::Guard::doPush(unsigned d) noexcept
|
||||
{
|
||||
xbit_ = xbit_ || (digits_ & 0x0000'0000'0000'000F) != 0;
|
||||
digits_ >>= 4;
|
||||
digits_ |= (d & 0x0000'0000'0000'000FULL) << 60;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline void
|
||||
Number::Guard::push(T d) noexcept
|
||||
{
|
||||
doPush(static_cast<unsigned>(d));
|
||||
}
|
||||
|
||||
inline unsigned
|
||||
Number::Guard::pop() noexcept
|
||||
{
|
||||
@@ -153,20 +195,51 @@ Number::Guard::round() noexcept
|
||||
|
||||
// Number
|
||||
|
||||
constexpr Number one{1000000000000000, -15, Number::unchecked{}};
|
||||
|
||||
void
|
||||
Number::normalize()
|
||||
constexpr Number
|
||||
Number::oneSmall()
|
||||
{
|
||||
return Number{
|
||||
Number::smallRange.min, -Number::smallRange.log, Number::unchecked{}};
|
||||
};
|
||||
|
||||
constexpr Number oneSml = Number::oneSmall();
|
||||
|
||||
constexpr Number
|
||||
Number::oneLarge()
|
||||
{
|
||||
return Number{
|
||||
Number::largeRange.min, -Number::largeRange.log, Number::unchecked{}};
|
||||
};
|
||||
|
||||
constexpr Number oneLrg = Number::oneLarge();
|
||||
|
||||
Number
|
||||
Number::one()
|
||||
{
|
||||
if (&range_.get() == &smallRange)
|
||||
return oneSml;
|
||||
XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_");
|
||||
return oneLrg;
|
||||
}
|
||||
|
||||
// Use the member names in this static function for now so the diff is cleaner
|
||||
void
|
||||
Number::normalize(
|
||||
internalrep& mantissa_,
|
||||
int& exponent_,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa)
|
||||
{
|
||||
constexpr Number zero = Number{};
|
||||
if (mantissa_ == 0)
|
||||
{
|
||||
*this = Number{};
|
||||
mantissa_ = zero.mantissa_;
|
||||
exponent_ = zero.exponent_;
|
||||
return;
|
||||
}
|
||||
bool const negative = (mantissa_ < 0);
|
||||
auto m = static_cast<std::make_unsigned_t<rep>>(mantissa_);
|
||||
if (negative)
|
||||
m = -m;
|
||||
auto m = static_cast<std::make_unsigned_t<internalrep>>(
|
||||
negative ? -mantissa_ : mantissa_);
|
||||
while ((m < minMantissa) && (exponent_ > minExponent))
|
||||
{
|
||||
m *= 10;
|
||||
@@ -186,7 +259,8 @@ Number::normalize()
|
||||
mantissa_ = m;
|
||||
if ((exponent_ < minExponent) || (mantissa_ < minMantissa))
|
||||
{
|
||||
*this = Number{};
|
||||
mantissa_ = zero.mantissa_;
|
||||
exponent_ = zero.exponent_;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -207,6 +281,13 @@ Number::normalize()
|
||||
mantissa_ = -mantissa_;
|
||||
}
|
||||
|
||||
void
|
||||
Number::normalize()
|
||||
{
|
||||
normalize(
|
||||
mantissa_, exponent_, Number::minMantissa(), Number::maxMantissa());
|
||||
}
|
||||
|
||||
Number&
|
||||
Number::operator+=(Number const& y)
|
||||
{
|
||||
@@ -266,6 +347,8 @@ Number::operator+=(Number const& y)
|
||||
}
|
||||
if (xn == yn)
|
||||
{
|
||||
auto const maxMantissa = Number::maxMantissa();
|
||||
|
||||
xm += ym;
|
||||
if (xm > maxMantissa)
|
||||
{
|
||||
@@ -288,6 +371,8 @@ Number::operator+=(Number const& y)
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const minMantissa = Number::minMantissa();
|
||||
|
||||
if (xm > ym)
|
||||
{
|
||||
xm = xm - ym;
|
||||
@@ -388,6 +473,9 @@ Number::operator*=(Number const& y)
|
||||
Guard g;
|
||||
if (zn == -1)
|
||||
g.set_negative();
|
||||
|
||||
auto const maxMantissa = Number::maxMantissa();
|
||||
|
||||
while (zm > maxMantissa)
|
||||
{
|
||||
// The following is optimization for:
|
||||
@@ -396,7 +484,7 @@ Number::operator*=(Number const& y)
|
||||
g.push(divu10(zm));
|
||||
++ze;
|
||||
}
|
||||
xm = static_cast<rep>(zm);
|
||||
xm = static_cast<internalrep>(zm);
|
||||
xe = ze;
|
||||
auto r = g.round();
|
||||
if (r == 1 || (r == 0 && (xm & 1) == 1))
|
||||
@@ -448,11 +536,66 @@ 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));
|
||||
exponent_ = ne - de - 17;
|
||||
// Shift by 10^17 gives greatest precision while not overflowing
|
||||
// uint128_t or the cast back to int64_t
|
||||
// TODO: Can/should this be made bigger for largeRange?
|
||||
// log(2^128,10) ~ 38.5
|
||||
// largeRange.log = 18, fits in 10^19
|
||||
// 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;
|
||||
uint128_t const f =
|
||||
small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
|
||||
XRPL_ASSERT_PARTS(
|
||||
f >= Number::minMantissa() * 10,
|
||||
"Number::operator/=",
|
||||
"factor expected size");
|
||||
|
||||
// unsigned denominator
|
||||
auto const dmu = static_cast<uint128_t>(dm);
|
||||
// correctionFactor can be anything between 10 and f, depending on how much
|
||||
// extra precision we want to only use for rounding with the
|
||||
// largeMantissa. Three digits seems like plenty, and is more than
|
||||
// the smallMantissa uses.
|
||||
uint128_t const correctionFactor = 1'000;
|
||||
|
||||
auto const numerator = uint128_t(nm) * f;
|
||||
|
||||
mantissa_ = numerator / dmu;
|
||||
exponent_ = ne - de - (small ? 17 : 19);
|
||||
if (!small)
|
||||
{
|
||||
// Virtually multiply numerator by correctionFactor. Since that would
|
||||
// overflow in the existing uint128_t, we'll do that part separately.
|
||||
// The math for this would work for small mantissas, but we need to
|
||||
// preserve existing behavior.
|
||||
//
|
||||
// Consider:
|
||||
// ((numerator * correctionFactor) / dmu) / correctionFactor
|
||||
// = ((numerator / dmu) * correctionFactor) / correctionFactor)
|
||||
//
|
||||
// But that assumes infinite precision. With integer math, this is
|
||||
// equivalent to
|
||||
//
|
||||
// = ((numerator / dmu * correctionFactor)
|
||||
// + ((numerator % dmu) * correctionFactor) / dmu) / correctionFactor
|
||||
//
|
||||
// We have already set `mantissa_ = numerator / dmu`. Now we
|
||||
// compute `remainder = numerator % dmu`, and if it is
|
||||
// nonzero, we do the rest of the arithmetic. If it's zero, we can skip
|
||||
// it.
|
||||
auto const remainder = (numerator % dmu);
|
||||
if (remainder != 0)
|
||||
{
|
||||
mantissa_ *= correctionFactor;
|
||||
auto const correction = remainder * correctionFactor / dmu;
|
||||
mantissa_ += correction;
|
||||
// divide by 1000 by moving the exponent, so we don't lose the
|
||||
// integer value we just computed
|
||||
exponent_ -= 3;
|
||||
}
|
||||
}
|
||||
mantissa_ *= np * dp;
|
||||
normalize();
|
||||
return *this;
|
||||
@@ -460,7 +603,7 @@ Number::operator/=(Number const& y)
|
||||
|
||||
Number::operator rep() const
|
||||
{
|
||||
rep drops = mantissa_;
|
||||
internalrep drops = mantissa_;
|
||||
int offset = exponent_;
|
||||
Guard g;
|
||||
if (drops != 0)
|
||||
@@ -475,9 +618,16 @@ Number::operator rep() const
|
||||
g.push(drops % 10);
|
||||
drops /= 10;
|
||||
}
|
||||
if (offset == 0 && drops > std::numeric_limits<rep>::max())
|
||||
{
|
||||
// If offset == 0, then the loop won't run, and the overflow check
|
||||
// won't be made, but a int128 can overflow int64 by itself, so
|
||||
// check here.
|
||||
throw std::overflow_error("Number::operator rep() overflow");
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -489,7 +639,7 @@ Number::operator rep() const
|
||||
if (g.is_negative())
|
||||
drops = -drops;
|
||||
}
|
||||
return drops;
|
||||
return static_cast<rep>(drops);
|
||||
}
|
||||
|
||||
std::string
|
||||
@@ -500,30 +650,41 @@ to_string(Number const& amount)
|
||||
return "0";
|
||||
|
||||
auto const exponent = amount.exponent();
|
||||
auto mantissa = amount.mantissa();
|
||||
|
||||
bool const negative = amount.mantissa() < 0;
|
||||
|
||||
auto const mantissa = [&]() {
|
||||
auto mantissa = amount.mantissa();
|
||||
if (negative)
|
||||
{
|
||||
mantissa = -mantissa;
|
||||
}
|
||||
XRPL_ASSERT(
|
||||
mantissa < std::numeric_limits<std::uint64_t>::max(),
|
||||
"ripple::to_string(Number) : mantissa fits in uin64_t");
|
||||
return static_cast<std::uint64_t>(mantissa);
|
||||
}();
|
||||
|
||||
// Use scientific notation for exponents that are too small or too large
|
||||
if (((exponent != 0) && ((exponent < -25) || (exponent > -5))))
|
||||
auto const rangeLog = Number::mantissaLog();
|
||||
if (((exponent != 0) &&
|
||||
((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
|
||||
{
|
||||
std::string ret = std::to_string(mantissa);
|
||||
std::string ret = negative ? "-" : "";
|
||||
ret.append(std::to_string(mantissa));
|
||||
ret.append(1, 'e');
|
||||
ret.append(std::to_string(exponent));
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool negative = false;
|
||||
|
||||
if (mantissa < 0)
|
||||
{
|
||||
mantissa = -mantissa;
|
||||
negative = true;
|
||||
}
|
||||
|
||||
// TODO: These numbers are probably wrong for largeRange
|
||||
XRPL_ASSERT(
|
||||
exponent + 43 > 0, "ripple::to_string(Number) : minimum exponent");
|
||||
|
||||
ptrdiff_t const pad_prefix = 27;
|
||||
ptrdiff_t const pad_suffix = 23;
|
||||
auto const mantissaLog = Number::mantissaLog();
|
||||
|
||||
ptrdiff_t const pad_prefix = mantissaLog + 12;
|
||||
ptrdiff_t const pad_suffix = mantissaLog + 8;
|
||||
|
||||
std::string const raw_value(std::to_string(mantissa));
|
||||
std::string val;
|
||||
@@ -533,7 +694,7 @@ to_string(Number const& amount)
|
||||
val.append(raw_value);
|
||||
val.append(pad_suffix, '0');
|
||||
|
||||
ptrdiff_t const offset(exponent + 43);
|
||||
ptrdiff_t const offset(exponent + pad_prefix + mantissaLog + 1);
|
||||
|
||||
auto pre_from(val.begin());
|
||||
auto const pre_to(val.begin() + offset);
|
||||
@@ -594,7 +755,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 +777,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)
|
||||
@@ -632,7 +795,7 @@ root(Number f, unsigned d)
|
||||
return f;
|
||||
|
||||
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
|
||||
auto e = f.exponent() + 16;
|
||||
auto e = f.exponent() + Number::mantissaLog() + 1;
|
||||
auto const di = static_cast<int>(d);
|
||||
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
|
||||
{
|
||||
@@ -681,6 +844,8 @@ root(Number f, unsigned d)
|
||||
Number
|
||||
root2(Number f)
|
||||
{
|
||||
auto const one = Number::one();
|
||||
|
||||
if (f == one)
|
||||
return f;
|
||||
if (f < Number{})
|
||||
@@ -689,7 +854,7 @@ root2(Number f)
|
||||
return f;
|
||||
|
||||
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
|
||||
auto e = f.exponent() + 16;
|
||||
auto e = f.exponent() + Number::mantissaLog() + 1;
|
||||
if (e % 2 != 0)
|
||||
++e;
|
||||
f = Number{f.mantissa(), f.exponent() - e}; // f /= 10^e;
|
||||
@@ -721,6 +886,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);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
//
|
||||
#include <xrpl/basics/LocalValue.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
|
||||
@@ -40,11 +42,19 @@ setSTNumberSwitchover(bool v)
|
||||
}
|
||||
|
||||
/* The range for the mantissa when normalized */
|
||||
static std::int64_t constexpr minMantissa = 1000000000000000ull;
|
||||
static std::int64_t constexpr maxMantissa = 9999999999999999ull;
|
||||
// log(2^63,10) ~ 18.96
|
||||
//
|
||||
static std::int64_t constexpr minMantissa = STAmount::cMinValue;
|
||||
static std::int64_t constexpr maxMantissa = STAmount::cMaxValue;
|
||||
/* The range for the exponent when normalized */
|
||||
static int constexpr minExponent = -96;
|
||||
static int constexpr maxExponent = 80;
|
||||
static int constexpr minExponent = STAmount::cMinOffset;
|
||||
static int constexpr maxExponent = STAmount::cMaxOffset;
|
||||
|
||||
std::pair<IOUAmount::mantissa_type, IOUAmount::exponent_type>
|
||||
IOUAmount::scaleNumber(Number const& number)
|
||||
{
|
||||
return number.normalizeToRange(minMantissa, maxMantissa);
|
||||
}
|
||||
|
||||
IOUAmount
|
||||
IOUAmount::minPositiveAmount()
|
||||
@@ -64,8 +74,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 +115,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");
|
||||
|
||||
@@ -49,6 +49,12 @@ Issue::native() const
|
||||
return *this == xrpIssue();
|
||||
}
|
||||
|
||||
bool
|
||||
Issue::integral() const
|
||||
{
|
||||
return native();
|
||||
}
|
||||
|
||||
bool
|
||||
isConsistent(Issue const& ac)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
//
|
||||
#include <xrpl/basics/LocalValue.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/hardened_hash.h>
|
||||
#include <xrpl/beast/hash/uhash.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
|
||||
#include <memory>
|
||||
@@ -33,6 +35,15 @@ getCurrentTransactionRules()
|
||||
void
|
||||
setCurrentTransactionRules(std::optional<Rules> r)
|
||||
{
|
||||
// Make global changes associated with the rules before the value is moved.
|
||||
// Push the appropriate setting, instead of having the class pull every time
|
||||
// the value is needed. That could get expensive fast.
|
||||
bool enableLargeNumbers = !r ||
|
||||
(r->enabled(featureSingleAssetVault) /*||
|
||||
r->enabled(featureLendingProtocol)*/);
|
||||
Number::setMantissaScale(
|
||||
enableLargeNumbers ? MantissaRange::large : MantissaRange::small);
|
||||
|
||||
*getCurrentTransactionRulesRef() = std::move(r);
|
||||
}
|
||||
|
||||
|
||||
@@ -310,8 +310,8 @@ STAmount&
|
||||
STAmount::operator=(IOUAmount const& iou)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
native() == false,
|
||||
"ripple::STAmount::operator=(IOUAmount) : is not XRP");
|
||||
integral() == false,
|
||||
"ripple::STAmount::operator=(IOUAmount) : is not integral");
|
||||
mOffset = iou.exponent();
|
||||
mIsNegative = iou < beast::zero;
|
||||
if (mIsNegative)
|
||||
@@ -851,8 +851,9 @@ STAmount::canonicalize()
|
||||
|
||||
if (getSTNumberSwitchover())
|
||||
{
|
||||
auto const value = unsafe_cast<std::int64_t>(mValue);
|
||||
Number num(
|
||||
mIsNegative ? -mValue : mValue, mOffset, Number::unchecked{});
|
||||
mIsNegative ? -value : value, mOffset, Number::unchecked{});
|
||||
auto set = [&](auto const& val) {
|
||||
mIsNegative = val.value() < 0;
|
||||
mValue = mIsNegative ? -val.value() : val.value();
|
||||
@@ -1323,7 +1324,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();
|
||||
|
||||
@@ -50,8 +50,27 @@ STNumber::add(Serializer& s) const
|
||||
XRPL_ASSERT(
|
||||
getFName().fieldType == getSType(),
|
||||
"ripple::STNumber::add : field type match");
|
||||
s.add64(value_.mantissa());
|
||||
s.add32(value_.exponent());
|
||||
if (value_.mantissa() <= std::numeric_limits<std::int64_t>::max() &&
|
||||
value_.mantissa() >= std::numeric_limits<std::int64_t>::min())
|
||||
{
|
||||
// If the mantissa fits in the range of std::int64_t, write it directly.
|
||||
// This preserves the maximum available precision.
|
||||
// With the small range, all numbers should be written this way. With
|
||||
// the large range, it's likely that most numbers will be written this
|
||||
// way.
|
||||
s.add64(static_cast<std::int64_t>(value_.mantissa()));
|
||||
s.add32(value_.exponent());
|
||||
}
|
||||
else
|
||||
{
|
||||
constexpr std::int64_t min = 100'000'000'000'000'000LL;
|
||||
constexpr std::int64_t max = min * 10 - 1;
|
||||
static_assert(
|
||||
min < (std::numeric_limits<std::int64_t>::max() - 1 / 10));
|
||||
auto const [mantissa, exponent] = value_.normalizeToRange(min, max);
|
||||
s.add64(mantissa);
|
||||
s.add32(exponent);
|
||||
}
|
||||
}
|
||||
|
||||
Number const&
|
||||
@@ -180,16 +199,18 @@ numberFromJson(SField const& field, Json::Value const& value)
|
||||
else if (value.isString())
|
||||
{
|
||||
parts = partsFromString(value.asString());
|
||||
// Only strings can represent out-of-range values.
|
||||
if (parts.mantissa > std::numeric_limits<std::int64_t>::max())
|
||||
Throw<std::range_error>("too high");
|
||||
// Number mantissas are much bigger than the allowable parsed values, so
|
||||
// it can't be out of range.
|
||||
static_assert(
|
||||
std::numeric_limits<numberint128>::max() >
|
||||
std::numeric_limits<decltype(parts.mantissa)>::max());
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw<std::runtime_error>("not a number");
|
||||
}
|
||||
|
||||
std::int64_t mantissa = parts.mantissa;
|
||||
numberint128 mantissa = parts.mantissa;
|
||||
if (parts.negative)
|
||||
mantissa = -mantissa;
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ namespace test {
|
||||
*/
|
||||
struct AMM_test : public jtx::AMMTest
|
||||
{
|
||||
// Use small Number mantissas for the life of this test.
|
||||
NumberMantissaScaleGuard sg_{ripple::MantissaRange::small};
|
||||
|
||||
private:
|
||||
void
|
||||
testInstanceCreate()
|
||||
@@ -1384,15 +1387,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 +1419,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(
|
||||
@@ -3014,6 +3014,11 @@ private:
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
// For now, just disable SAV entirely, which locks in the small Number
|
||||
// mantissas
|
||||
features =
|
||||
features - featureSingleAssetVault /* - featureLendingProtocol */;
|
||||
|
||||
// Auction slot initially is owned by AMM creator, who pays 0 price.
|
||||
|
||||
// Bid 110 tokens. Pay bidMin.
|
||||
@@ -3758,6 +3763,11 @@ private:
|
||||
testcase("Basic Payment");
|
||||
using namespace jtx;
|
||||
|
||||
// For now, just disable SAV entirely, which locks in the small Number
|
||||
// mantissas
|
||||
features =
|
||||
features - featureSingleAssetVault /* - featureLendingProtocol */;
|
||||
|
||||
// Payment 100USD for 100XRP.
|
||||
// Force one path with tfNoRippleDirect.
|
||||
testAMM(
|
||||
@@ -6476,6 +6486,8 @@ private:
|
||||
Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
|
||||
auto rules = env.current()->rules();
|
||||
CurrentTransactionRulesGuard rg(rules);
|
||||
NumberMantissaScaleGuard sg(MantissaRange::small);
|
||||
|
||||
for (auto const& t : tests)
|
||||
{
|
||||
auto getPool = [&](std::string const& v, bool isXRP) {
|
||||
|
||||
@@ -2474,7 +2474,7 @@ class MPToken_test : public beast::unit_test::suite
|
||||
alice.name(), makeMptID(env.seq(alice), alice));
|
||||
|
||||
Json::Value jv = claw(alice, mpt(1), bob);
|
||||
jv[jss::Amount][jss::value] = to_string(maxMPTokenAmount + 1);
|
||||
jv[jss::Amount][jss::value] = std::to_string(maxMPTokenAmount + 1);
|
||||
Json::Value jv1;
|
||||
jv1[jss::secret] = alice.name();
|
||||
jv1[jss::tx_json] = jv;
|
||||
|
||||
@@ -4525,7 +4525,7 @@ class Vault_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(checkString(vault, sfAssetsAvailable, "50"));
|
||||
BEAST_EXPECT(checkString(vault, sfAssetsMaximum, "1000"));
|
||||
BEAST_EXPECT(checkString(vault, sfAssetsTotal, "50"));
|
||||
BEAST_EXPECT(checkString(vault, sfLossUnrealized, "0"));
|
||||
BEAST_EXPECT(!vault.isMember(sfLossUnrealized.getJsonName()));
|
||||
|
||||
auto const strShareID = strHex(sle->at(sfShareMPTID));
|
||||
BEAST_EXPECT(checkString(vault, sfShareMPTID, strShareID));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 convertible 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.
|
||||
|
||||
|
||||
@@ -29,10 +29,8 @@ struct STNumber_test : public beast::unit_test::suite
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
doRun()
|
||||
{
|
||||
static_assert(!std::is_convertible_v<STNumber*, Number*>);
|
||||
|
||||
{
|
||||
STNumber const stnum{sfNumber};
|
||||
BEAST_EXPECT(stnum.getSType() == STI_NUMBER);
|
||||
@@ -127,6 +125,40 @@ struct STNumber_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(
|
||||
numberFromJson(sfNumber, "-0.000e6") == STNumber(sfNumber, 0));
|
||||
|
||||
{
|
||||
NumberRoundModeGuard mg(Number::towards_zero);
|
||||
// maxint64 9,223,372,036,854,775,807
|
||||
auto const maxInt =
|
||||
std::to_string(std::numeric_limits<std::int64_t>::max());
|
||||
// minint64 -9,223,372,036,854,775,808
|
||||
auto const minInt =
|
||||
std::to_string(std::numeric_limits<std::int64_t>::min());
|
||||
if (Number::getMantissaScale() == MantissaRange::small)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
numberFromJson(sfNumber, maxInt) ==
|
||||
STNumber(sfNumber, Number{9'223'372'036'854'775, 3}));
|
||||
BEAST_EXPECT(
|
||||
numberFromJson(sfNumber, minInt) ==
|
||||
STNumber(sfNumber, Number{-9'223'372'036'854'775, 3}));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
numberFromJson(sfNumber, maxInt) ==
|
||||
STNumber(
|
||||
sfNumber, Number{9'223'372'036'854'775'807, 0}));
|
||||
BEAST_EXPECT(
|
||||
numberFromJson(sfNumber, minInt) ==
|
||||
STNumber(
|
||||
sfNumber,
|
||||
-Number{
|
||||
numberint128(9'223'372'036'854'775) * 1000 +
|
||||
808,
|
||||
0}));
|
||||
}
|
||||
}
|
||||
|
||||
constexpr auto imin = std::numeric_limits<int>::min();
|
||||
BEAST_EXPECT(
|
||||
numberFromJson(sfNumber, imin) ==
|
||||
@@ -279,15 +311,21 @@ struct STNumber_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
static_assert(!std::is_convertible_v<STNumber*, Number*>);
|
||||
|
||||
for (auto const scale : {MantissaRange::small, MantissaRange::large})
|
||||
{
|
||||
NumberMantissaScaleGuard sg(scale);
|
||||
testcase << to_string(Number::getMantissaScale());
|
||||
doRun();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(STNumber, protocol, ripple);
|
||||
|
||||
void
|
||||
testCompile(std::ostream& out)
|
||||
{
|
||||
STNumber number{sfNumber, 42};
|
||||
out << number;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -592,6 +592,24 @@ Transactor::ticketDelete(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
Transactor::useOldNumberRules(TxType txType)
|
||||
{
|
||||
constexpr auto skipTransactions = std::to_array<TxType>(
|
||||
{ttAMM_BID,
|
||||
ttAMM_CLAWBACK,
|
||||
ttAMM_CREATE,
|
||||
ttAMM_DELETE,
|
||||
ttAMM_DEPOSIT,
|
||||
ttAMM_VOTE,
|
||||
ttAMM_WITHDRAW});
|
||||
|
||||
return std::find(
|
||||
std::begin(skipTransactions),
|
||||
std::end(skipTransactions),
|
||||
txType) != std::end(skipTransactions);
|
||||
}
|
||||
|
||||
// check stuff before you bother to lock the ledger
|
||||
void
|
||||
Transactor::preCompute()
|
||||
@@ -1106,10 +1124,16 @@ Transactor::operator()()
|
||||
{
|
||||
JLOG(j_.trace()) << "apply: " << ctx_.tx.getTransactionID();
|
||||
|
||||
// These global updates really should have been for every Transaction
|
||||
// step: preflight, preclaim, and doApply. And even calculateBaseFee. See
|
||||
// with_txn_type().
|
||||
//
|
||||
// raii classes for the current ledger rules.
|
||||
// fixUniversalNumber predate the rulesGuard and should be replaced.
|
||||
NumberSO stNumberSO{view().rules().enabled(fixUniversalNumber)};
|
||||
CurrentTransactionRulesGuard currentTransctionRulesGuard(view().rules());
|
||||
if (Transactor::useOldNumberRules(ctx_.tx.getTxnType()))
|
||||
Number::setMantissaScale(MantissaRange::small);
|
||||
|
||||
#ifdef DEBUG
|
||||
{
|
||||
@@ -1122,7 +1146,7 @@ Transactor::operator()()
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Transaction serdes mismatch";
|
||||
JLOG(j_.info()) << to_string(ctx_.tx.getJson(JsonOptions::none));
|
||||
JLOG(j_.fatal()) << ctx_.tx.getJson(JsonOptions::none);
|
||||
JLOG(j_.fatal()) << s2.getJson(JsonOptions::none);
|
||||
UNREACHABLE(
|
||||
"ripple::Transactor::operator() : transaction serdes mismatch");
|
||||
|
||||
@@ -230,6 +230,9 @@ public:
|
||||
uint256 const& ticketIndex,
|
||||
beast::Journal j);
|
||||
|
||||
static bool
|
||||
useOldNumberRules(TxType txType);
|
||||
|
||||
protected:
|
||||
TER
|
||||
apply();
|
||||
|
||||
@@ -34,8 +34,33 @@ struct UnknownTxnType : std::exception
|
||||
// throw an "UnknownTxnType" exception on error
|
||||
template <class F>
|
||||
auto
|
||||
with_txn_type(TxType txnType, F&& f)
|
||||
with_txn_type(Rules const& rules, TxType txnType, F&& f)
|
||||
{
|
||||
// These global updates really should have been for every Transaction
|
||||
// step: preflight, preclaim, calculateBaseFee, and doApply. Unfortunately,
|
||||
// they were only included in doApply (via Transactor::operator()). That may
|
||||
// have been sufficient when the changes were only related to operations
|
||||
// that mutated data, but some features will now change how they read data,
|
||||
// so these need to be more global.
|
||||
//
|
||||
// To prevent unintentional side effects on existing checks, they will be
|
||||
// set for every operation only once SingleAssetVault (or later
|
||||
// LendingProtocol) are enabled.
|
||||
//
|
||||
// See also Transactor::operator().
|
||||
//
|
||||
std::optional<NumberSO> stNumberSO;
|
||||
std::optional<CurrentTransactionRulesGuard> rulesGuard;
|
||||
if (rules.enabled(featureSingleAssetVault) /*|| rules.enabled(featureLendingProtocol)*/)
|
||||
{
|
||||
// raii classes for the current ledger rules.
|
||||
// fixUniversalNumber predate the rulesGuard and should be replaced.
|
||||
stNumberSO.emplace(rules.enabled(fixUniversalNumber));
|
||||
rulesGuard.emplace(rules);
|
||||
}
|
||||
if (Transactor::useOldNumberRules(txnType))
|
||||
Number::setMantissaScale(MantissaRange::small);
|
||||
|
||||
switch (txnType)
|
||||
{
|
||||
#pragma push_macro("TRANSACTION")
|
||||
@@ -99,7 +124,7 @@ invoke_preflight(PreflightContext const& ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() {
|
||||
return with_txn_type(ctx.rules, ctx.tx.getTxnType(), [&]<typename T>() {
|
||||
auto const tec = Transactor::invokePreflight<T>(ctx);
|
||||
return std::make_pair(
|
||||
tec,
|
||||
@@ -126,50 +151,51 @@ invoke_preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// use name hiding to accomplish compile-time polymorphism of static
|
||||
// class functions for Transactor and derived classes.
|
||||
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() -> TER {
|
||||
// preclaim functionality is divided into two sections:
|
||||
// 1. Up to and including the signature check: returns NotTEC.
|
||||
// All transaction checks before and including checkSign
|
||||
// MUST return NotTEC, or something more restrictive.
|
||||
// Allowing tec results in these steps risks theft or
|
||||
// destruction of funds, as a fee will be charged before the
|
||||
// signature is checked.
|
||||
// 2. After the signature check: returns TER.
|
||||
return with_txn_type(
|
||||
ctx.view.rules(), ctx.tx.getTxnType(), [&]<typename T>() -> TER {
|
||||
// preclaim functionality is divided into two sections:
|
||||
// 1. Up to and including the signature check: returns NotTEC.
|
||||
// All transaction checks before and including checkSign
|
||||
// MUST return NotTEC, or something more restrictive.
|
||||
// Allowing tec results in these steps risks theft or
|
||||
// destruction of funds, as a fee will be charged before the
|
||||
// signature is checked.
|
||||
// 2. After the signature check: returns TER.
|
||||
|
||||
// If the transactor requires a valid account and the
|
||||
// transaction doesn't list one, preflight will have already
|
||||
// a flagged a failure.
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
// If the transactor requires a valid account and the
|
||||
// transaction doesn't list one, preflight will have already
|
||||
// a flagged a failure.
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
|
||||
if (id != beast::zero)
|
||||
{
|
||||
if (NotTEC const preSigResult = [&]() -> NotTEC {
|
||||
if (NotTEC const result =
|
||||
T::checkSeqProxy(ctx.view, ctx.tx, ctx.j))
|
||||
return result;
|
||||
if (id != beast::zero)
|
||||
{
|
||||
if (NotTEC const preSigResult = [&]() -> NotTEC {
|
||||
if (NotTEC const result =
|
||||
T::checkSeqProxy(ctx.view, ctx.tx, ctx.j))
|
||||
return result;
|
||||
|
||||
if (NotTEC const result =
|
||||
T::checkPriorTxAndLastLedger(ctx))
|
||||
return result;
|
||||
if (NotTEC const result =
|
||||
T::checkPriorTxAndLastLedger(ctx))
|
||||
return result;
|
||||
|
||||
if (NotTEC const result =
|
||||
T::checkPermission(ctx.view, ctx.tx))
|
||||
return result;
|
||||
if (NotTEC const result =
|
||||
T::checkPermission(ctx.view, ctx.tx))
|
||||
return result;
|
||||
|
||||
if (NotTEC const result = T::checkSign(ctx))
|
||||
return result;
|
||||
if (NotTEC const result = T::checkSign(ctx))
|
||||
return result;
|
||||
|
||||
return tesSUCCESS;
|
||||
}())
|
||||
return preSigResult;
|
||||
return tesSUCCESS;
|
||||
}())
|
||||
return preSigResult;
|
||||
|
||||
if (TER const result =
|
||||
T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)))
|
||||
return result;
|
||||
}
|
||||
if (TER const result = T::checkFee(
|
||||
ctx, calculateBaseFee(ctx.view, ctx.tx)))
|
||||
return result;
|
||||
}
|
||||
|
||||
return T::preclaim(ctx);
|
||||
});
|
||||
return T::preclaim(ctx);
|
||||
});
|
||||
}
|
||||
catch (UnknownTxnType const& e)
|
||||
{
|
||||
@@ -204,7 +230,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
try
|
||||
{
|
||||
return with_txn_type(tx.getTxnType(), [&]<typename T>() {
|
||||
return with_txn_type(view.rules(), tx.getTxnType(), [&]<typename T>() {
|
||||
return T::calculateBaseFee(view, tx);
|
||||
});
|
||||
}
|
||||
@@ -264,10 +290,11 @@ invoke_apply(ApplyContext& ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() {
|
||||
T p(ctx);
|
||||
return p();
|
||||
});
|
||||
return with_txn_type(
|
||||
ctx.view().rules(), ctx.tx.getTxnType(), [&]<typename T>() {
|
||||
T p(ctx);
|
||||
return p();
|
||||
});
|
||||
}
|
||||
catch (UnknownTxnType const& e)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user