mirror of
https://github.com/XRPLF/rippled.git
synced 2026-01-30 19:45:24 +00:00
Compare commits
27 Commits
develop
...
ximinez/nu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c499aacab | ||
|
|
0364d61b42 | ||
|
|
f0eef6ec63 | ||
|
|
7e2c2573d2 | ||
|
|
a48182c65d | ||
|
|
e7f81c2646 | ||
|
|
5f8acbc001 | ||
|
|
47bfd15004 | ||
|
|
accbe7343d | ||
|
|
cd4f915e40 | ||
|
|
f8d441bb6b | ||
|
|
2f0690f3c5 | ||
|
|
040bf34257 | ||
|
|
5c87c4ffb0 | ||
|
|
0a9436def4 | ||
|
|
e5646e4ebe | ||
|
|
f76bf5340c | ||
|
|
1af0f4bd43 | ||
|
|
c6821ab842 | ||
|
|
7ab9709373 | ||
|
|
aa12210fcd | ||
|
|
9235ec483a | ||
|
|
ffe0a3cc61 | ||
|
|
add9071b20 | ||
|
|
465e7b6d91 | ||
|
|
6223ebe05e | ||
|
|
4fe50c2d31 |
@@ -10,6 +10,10 @@
|
|||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <boost/multiprecision/cpp_int.hpp>
|
||||||
|
#endif // !defined(_MSC_VER)
|
||||||
|
|
||||||
namespace xrpl {
|
namespace xrpl {
|
||||||
|
|
||||||
class Number;
|
class Number;
|
||||||
@@ -17,18 +21,37 @@ class Number;
|
|||||||
std::string
|
std::string
|
||||||
to_string(Number const& amount);
|
to_string(Number const& amount);
|
||||||
|
|
||||||
|
/** Returns a rough estimate of log10(value).
|
||||||
|
*
|
||||||
|
* The return value is a pair (log, rem), where log is the estimated log10,
|
||||||
|
* and rem is value divided by 10^log. If rem is 1, then value is an exact
|
||||||
|
* power of ten, and log is the exact log10(value).
|
||||||
|
*
|
||||||
|
* This function only works for positive values.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
constexpr std::pair<int, T>
|
||||||
|
logTenEstimate(T value)
|
||||||
|
{
|
||||||
|
int log = 0;
|
||||||
|
T remainder = value;
|
||||||
|
while (value >= 10)
|
||||||
|
{
|
||||||
|
if (value % 10 == 0)
|
||||||
|
remainder = remainder / 10;
|
||||||
|
value /= 10;
|
||||||
|
++log;
|
||||||
|
}
|
||||||
|
return {log, remainder};
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
constexpr std::optional<int>
|
constexpr std::optional<int>
|
||||||
logTen(T value)
|
logTen(T value)
|
||||||
{
|
{
|
||||||
int log = 0;
|
auto const est = logTenEstimate(value);
|
||||||
while (value >= 10 && value % 10 == 0)
|
if (est.second == 1)
|
||||||
{
|
return est.first;
|
||||||
value /= 10;
|
|
||||||
++log;
|
|
||||||
}
|
|
||||||
if (value == 1)
|
|
||||||
return log;
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,12 +65,10 @@ isPowerOfTen(T value)
|
|||||||
/** MantissaRange defines a range for the mantissa of a normalized Number.
|
/** MantissaRange defines a range for the mantissa of a normalized Number.
|
||||||
*
|
*
|
||||||
* The mantissa is in the range [min, max], where
|
* The mantissa is in the range [min, max], where
|
||||||
* * min is a power of 10, and
|
|
||||||
* * max = min * 10 - 1.
|
|
||||||
*
|
*
|
||||||
* The mantissa_scale enum indicates whether the range is "small" or "large".
|
* The mantissa_scale enum indicates whether the range is "small" or "large".
|
||||||
* This intentionally restricts the number of MantissaRanges that can be
|
* This intentionally restricts the number of MantissaRanges that can be
|
||||||
* instantiated to two: one for each scale.
|
* used to two: one for each scale.
|
||||||
*
|
*
|
||||||
* The "small" scale is based on the behavior of STAmount for IOUs. It has a min
|
* The "small" scale is based on the behavior of STAmount for IOUs. It has a min
|
||||||
* value of 10^15, and a max value of 10^16-1. This was sufficient for
|
* value of 10^15, and a max value of 10^16-1. This was sufficient for
|
||||||
@@ -61,8 +82,8 @@ isPowerOfTen(T value)
|
|||||||
* "large" scale.
|
* "large" scale.
|
||||||
*
|
*
|
||||||
* The "large" scale is intended to represent all values that can be represented
|
* The "large" scale is intended to represent all values that can be represented
|
||||||
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max
|
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 2^63/10+1
|
||||||
* value of 10^19-1.
|
* (truncated), and a max value of 2^63-1.
|
||||||
*
|
*
|
||||||
* Note that if the mentioned amendments are eventually retired, this class
|
* Note that if the mentioned amendments are eventually retired, this class
|
||||||
* should be left in place, but the "small" scale option should be removed. This
|
* should be left in place, but the "small" scale option should be removed. This
|
||||||
@@ -74,28 +95,50 @@ struct MantissaRange
|
|||||||
enum mantissa_scale { small, large };
|
enum mantissa_scale { small, large };
|
||||||
|
|
||||||
explicit constexpr MantissaRange(mantissa_scale scale_)
|
explicit constexpr MantissaRange(mantissa_scale scale_)
|
||||||
: min(getMin(scale_))
|
: max(getMax(scale_))
|
||||||
, max(min * 10 - 1)
|
, min(computeMin(max))
|
||||||
, log(logTen(min).value_or(-1))
|
, referenceMin(getReferenceMin(scale_, min))
|
||||||
|
, log(computeLog(min))
|
||||||
, scale(scale_)
|
, scale(scale_)
|
||||||
{
|
{
|
||||||
|
// Since this is constexpr, if any of these throw, it won't compile
|
||||||
|
if (min * 10 <= max)
|
||||||
|
throw std::out_of_range("min * 10 <= max");
|
||||||
|
if (max / 10 >= min)
|
||||||
|
throw std::out_of_range("max / 10 >= min");
|
||||||
|
if ((min - 1) * 10 > max)
|
||||||
|
throw std::out_of_range("(min - 1) * 10 > max");
|
||||||
|
// This is a little hacky
|
||||||
|
if ((max + 10) / 10 < min)
|
||||||
|
throw std::out_of_range("(max + 10) / 10 < min");
|
||||||
}
|
}
|
||||||
|
|
||||||
rep min;
|
// Explicitly delete copy and move operations
|
||||||
|
MantissaRange(MantissaRange const&) = delete;
|
||||||
|
MantissaRange(MantissaRange&&) = delete;
|
||||||
|
MantissaRange&
|
||||||
|
operator=(MantissaRange const&) = delete;
|
||||||
|
MantissaRange&
|
||||||
|
operator=(MantissaRange&&) = delete;
|
||||||
|
|
||||||
rep max;
|
rep max;
|
||||||
|
rep min;
|
||||||
|
// This is not a great name. Used to determine if mantissas are in range,
|
||||||
|
// but have fewer digits than max
|
||||||
|
rep referenceMin;
|
||||||
int log;
|
int log;
|
||||||
mantissa_scale scale;
|
mantissa_scale scale;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr rep
|
static constexpr rep
|
||||||
getMin(mantissa_scale scale_)
|
getMax(mantissa_scale scale)
|
||||||
{
|
{
|
||||||
switch (scale_)
|
switch (scale)
|
||||||
{
|
{
|
||||||
case small:
|
case small:
|
||||||
return 1'000'000'000'000'000ULL;
|
return 9'999'999'999'999'999ULL;
|
||||||
case large:
|
case large:
|
||||||
return 1'000'000'000'000'000'000ULL;
|
return std::numeric_limits<std::int64_t>::max();
|
||||||
default:
|
default:
|
||||||
// Since this can never be called outside a non-constexpr
|
// Since this can never be called outside a non-constexpr
|
||||||
// context, this throw assures that the build fails if an
|
// context, this throw assures that the build fails if an
|
||||||
@@ -103,6 +146,33 @@ private:
|
|||||||
throw std::runtime_error("Unknown mantissa scale");
|
throw std::runtime_error("Unknown mantissa scale");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr rep
|
||||||
|
computeMin(rep max)
|
||||||
|
{
|
||||||
|
return max / 10 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr rep
|
||||||
|
getReferenceMin(mantissa_scale scale, rep min)
|
||||||
|
{
|
||||||
|
switch (scale)
|
||||||
|
{
|
||||||
|
case large:
|
||||||
|
return 1'000'000'000'000'000'000ULL;
|
||||||
|
default:
|
||||||
|
if (isPowerOfTen(min))
|
||||||
|
return min;
|
||||||
|
throw std::runtime_error("Unknown/bad mantissa scale");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr rep
|
||||||
|
computeLog(rep min)
|
||||||
|
{
|
||||||
|
auto const estimate = logTenEstimate(min);
|
||||||
|
return estimate.first + (estimate.second == 1 ? 0 : 1);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Like std::integral, but only 64-bit integral types.
|
// Like std::integral, but only 64-bit integral types.
|
||||||
@@ -110,6 +180,24 @@ template <class T>
|
|||||||
concept Integral64 =
|
concept Integral64 =
|
||||||
std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>;
|
std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>;
|
||||||
|
|
||||||
|
template <class STAmount, class Asset>
|
||||||
|
concept CanUseAsScale = requires(Asset a, Number n) { STAmount(a, n); } &&
|
||||||
|
requires(STAmount s) { s.exponent(); };
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
using uint128_t = boost::multiprecision::uint128_t;
|
||||||
|
using int128_t = boost::multiprecision::int128_t;
|
||||||
|
#else // !defined(_MSC_VER)
|
||||||
|
using uint128_t = __uint128_t;
|
||||||
|
using int128_t = __int128_t;
|
||||||
|
#endif // !defined(_MSC_VER)
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
concept UnsignedMantissa =
|
||||||
|
std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>;
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
/** Number is a floating point type that can represent a wide range of values.
|
/** Number is a floating point type that can represent a wide range of values.
|
||||||
*
|
*
|
||||||
* It can represent all values that can be represented by an STAmount -
|
* It can represent all values that can be represented by an STAmount -
|
||||||
@@ -137,9 +225,7 @@ concept Integral64 =
|
|||||||
* 1. Normalization can be disabled by using the "unchecked" ctor tag. This
|
* 1. Normalization can be disabled by using the "unchecked" ctor tag. This
|
||||||
* should only be used at specific conversion points, some constexpr
|
* should only be used at specific conversion points, some constexpr
|
||||||
* values, and in unit tests.
|
* values, and in unit tests.
|
||||||
* 2. The max of the "large" range, 10^19-1, is the largest 10^X-1 value that
|
* 2. The max of the "large" range, 2^63-1, TODO: explain the large range.
|
||||||
* fits in an unsigned 64-bit number. (10^19-1 < 2^64-1 and
|
|
||||||
* 10^20-1 > 2^64-1). This avoids under- and overflows.
|
|
||||||
*
|
*
|
||||||
* ---- External Interface ----
|
* ---- External Interface ----
|
||||||
*
|
*
|
||||||
@@ -153,7 +239,7 @@ concept Integral64 =
|
|||||||
*
|
*
|
||||||
* Note:
|
* Note:
|
||||||
* 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large"
|
* 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large"
|
||||||
* mantissa range.
|
* mantissa range. TODO: update this explanation.
|
||||||
* 2. The functions mantissa() and exponent() return the external view of the
|
* 2. The functions mantissa() and exponent() return the external view of the
|
||||||
* Number value, specifically using a signed 63-bit mantissa. This may
|
* Number value, specifically using a signed 63-bit mantissa. This may
|
||||||
* require altering the internal representation to fit into that range
|
* require altering the internal representation to fit into that range
|
||||||
@@ -213,8 +299,7 @@ class Number
|
|||||||
using rep = std::int64_t;
|
using rep = std::int64_t;
|
||||||
using internalrep = MantissaRange::rep;
|
using internalrep = MantissaRange::rep;
|
||||||
|
|
||||||
bool negative_{false};
|
rep mantissa_{0};
|
||||||
internalrep mantissa_{0};
|
|
||||||
int exponent_{std::numeric_limits<int>::lowest()};
|
int exponent_{std::numeric_limits<int>::lowest()};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -222,9 +307,11 @@ public:
|
|||||||
constexpr static int minExponent = -32768;
|
constexpr static int minExponent = -32768;
|
||||||
constexpr static int maxExponent = 32768;
|
constexpr static int maxExponent = 32768;
|
||||||
|
|
||||||
|
#if MAXREP
|
||||||
constexpr static internalrep maxRep = std::numeric_limits<rep>::max();
|
constexpr static internalrep maxRep = std::numeric_limits<rep>::max();
|
||||||
static_assert(maxRep == 9'223'372'036'854'775'807);
|
static_assert(maxRep == 9'223'372'036'854'775'807);
|
||||||
static_assert(-maxRep == std::numeric_limits<rep>::min() + 1);
|
static_assert(-maxRep == std::numeric_limits<rep>::min() + 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
// May need to make unchecked private
|
// May need to make unchecked private
|
||||||
struct unchecked
|
struct unchecked
|
||||||
@@ -269,6 +356,26 @@ public:
|
|||||||
constexpr int
|
constexpr int
|
||||||
exponent() const noexcept;
|
exponent() const noexcept;
|
||||||
|
|
||||||
|
/** Get the scale of this Number for the given asset.
|
||||||
|
*
|
||||||
|
* "scale" is similar to "exponent", but from the perspective of STAmount,
|
||||||
|
* which has different rules for determining the exponent than Number.
|
||||||
|
*
|
||||||
|
* Because Number does not have access to STAmount or Asset, this function
|
||||||
|
* is implemented as a template, with the expectation that it will only be
|
||||||
|
* used by those types. Any types that fit the requirements will work,
|
||||||
|
* though, if there's a need.
|
||||||
|
*
|
||||||
|
* @tparam STAmount The STAmount type.
|
||||||
|
* @tparam Asset The Asset type.
|
||||||
|
* @param asset The asset to use for determining the scale.
|
||||||
|
* @return The scale of this Number for the given asset.
|
||||||
|
*/
|
||||||
|
template <class STAmount, class Asset>
|
||||||
|
int
|
||||||
|
scale(Asset const& asset) const
|
||||||
|
requires CanUseAsScale<STAmount, Asset>;
|
||||||
|
|
||||||
constexpr Number
|
constexpr Number
|
||||||
operator+() const noexcept;
|
operator+() const noexcept;
|
||||||
constexpr Number
|
constexpr Number
|
||||||
@@ -310,8 +417,7 @@ public:
|
|||||||
friend constexpr bool
|
friend constexpr bool
|
||||||
operator==(Number const& x, Number const& y) noexcept
|
operator==(Number const& x, Number const& y) noexcept
|
||||||
{
|
{
|
||||||
return x.negative_ == y.negative_ && x.mantissa_ == y.mantissa_ &&
|
return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_;
|
||||||
x.exponent_ == y.exponent_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
friend constexpr bool
|
friend constexpr bool
|
||||||
@@ -325,8 +431,8 @@ public:
|
|||||||
{
|
{
|
||||||
// If the two amounts have different signs (zero is treated as positive)
|
// If the two amounts have different signs (zero is treated as positive)
|
||||||
// then the comparison is true iff the left is negative.
|
// then the comparison is true iff the left is negative.
|
||||||
bool const lneg = x.negative_;
|
bool const lneg = x.mantissa_ < 0;
|
||||||
bool const rneg = y.negative_;
|
bool const rneg = y.mantissa_ < 0;
|
||||||
|
|
||||||
if (lneg != rneg)
|
if (lneg != rneg)
|
||||||
return lneg;
|
return lneg;
|
||||||
@@ -354,7 +460,7 @@ public:
|
|||||||
constexpr int
|
constexpr int
|
||||||
signum() const noexcept
|
signum() const noexcept
|
||||||
{
|
{
|
||||||
return negative_ ? -1 : (mantissa_ ? 1 : 0);
|
return mantissa_ < 0 ? -1 : (mantissa_ ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Number
|
Number
|
||||||
@@ -393,6 +499,9 @@ public:
|
|||||||
friend Number
|
friend Number
|
||||||
root2(Number f);
|
root2(Number f);
|
||||||
|
|
||||||
|
friend Number
|
||||||
|
power(Number const& f, unsigned n, unsigned d);
|
||||||
|
|
||||||
// Thread local rounding control. Default is to_nearest
|
// Thread local rounding control. Default is to_nearest
|
||||||
enum rounding_mode { to_nearest, towards_zero, downward, upward };
|
enum rounding_mode { to_nearest, towards_zero, downward, upward };
|
||||||
static rounding_mode
|
static rounding_mode
|
||||||
@@ -457,22 +566,48 @@ private:
|
|||||||
static_assert(isPowerOfTen(smallRange.min));
|
static_assert(isPowerOfTen(smallRange.min));
|
||||||
static_assert(smallRange.min == 1'000'000'000'000'000LL);
|
static_assert(smallRange.min == 1'000'000'000'000'000LL);
|
||||||
static_assert(smallRange.max == 9'999'999'999'999'999LL);
|
static_assert(smallRange.max == 9'999'999'999'999'999LL);
|
||||||
|
static_assert(smallRange.referenceMin == smallRange.min);
|
||||||
static_assert(smallRange.log == 15);
|
static_assert(smallRange.log == 15);
|
||||||
|
#if MAXREP
|
||||||
static_assert(smallRange.min < maxRep);
|
static_assert(smallRange.min < maxRep);
|
||||||
static_assert(smallRange.max < maxRep);
|
static_assert(smallRange.max < maxRep);
|
||||||
|
#endif
|
||||||
constexpr static MantissaRange largeRange{MantissaRange::large};
|
constexpr static MantissaRange largeRange{MantissaRange::large};
|
||||||
static_assert(isPowerOfTen(largeRange.min));
|
static_assert(!isPowerOfTen(largeRange.min));
|
||||||
static_assert(largeRange.min == 1'000'000'000'000'000'000ULL);
|
static_assert(largeRange.min == 922'337'203'685'477'581ULL);
|
||||||
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL));
|
static_assert(largeRange.max == internalrep(9'223'372'036'854'775'807ULL));
|
||||||
|
static_assert(largeRange.max == std::numeric_limits<rep>::max());
|
||||||
|
static_assert(largeRange.referenceMin == 1'000'000'000'000'000'000ULL);
|
||||||
static_assert(largeRange.log == 18);
|
static_assert(largeRange.log == 18);
|
||||||
|
// There are 2 values that will not fit in largeRange without some extra
|
||||||
|
// work
|
||||||
|
// * 9223372036854775808
|
||||||
|
// * 9223372036854775809
|
||||||
|
// They both end up < min, but with a leftover. If they round up, everything
|
||||||
|
// will be fine. If they don't, well need to bring them up into range.
|
||||||
|
// Guard::bringIntoRange handles this situation.
|
||||||
|
|
||||||
|
#if MAXREP
|
||||||
static_assert(largeRange.min < maxRep);
|
static_assert(largeRange.min < maxRep);
|
||||||
static_assert(largeRange.max > maxRep);
|
static_assert(largeRange.max > maxRep);
|
||||||
|
#endif
|
||||||
|
|
||||||
// The range for the mantissa when normalized.
|
// The range for the mantissa when normalized.
|
||||||
// Use reference_wrapper to avoid making copies, and prevent accidentally
|
// Use reference_wrapper to avoid making copies, and prevent accidentally
|
||||||
// changing the values inside the range.
|
// changing the values inside the range.
|
||||||
static thread_local std::reference_wrapper<MantissaRange const> range_;
|
static thread_local std::reference_wrapper<MantissaRange const> range_;
|
||||||
|
|
||||||
|
// And one is needed because it needs to choose between oneSmall and
|
||||||
|
// oneLarge based on the current range
|
||||||
|
static Number
|
||||||
|
one(MantissaRange const& range);
|
||||||
|
|
||||||
|
static Number
|
||||||
|
root(MantissaRange const& range, Number f, unsigned d);
|
||||||
|
|
||||||
|
void
|
||||||
|
normalize(MantissaRange const& range);
|
||||||
|
|
||||||
void
|
void
|
||||||
normalize();
|
normalize();
|
||||||
|
|
||||||
@@ -495,11 +630,14 @@ private:
|
|||||||
friend void
|
friend void
|
||||||
doNormalize(
|
doNormalize(
|
||||||
bool& negative,
|
bool& negative,
|
||||||
T& mantissa_,
|
T& mantissa,
|
||||||
int& exponent_,
|
int& exponent,
|
||||||
MantissaRange::rep const& minMantissa,
|
MantissaRange::rep const& minMantissa,
|
||||||
MantissaRange::rep const& maxMantissa);
|
MantissaRange::rep const& maxMantissa);
|
||||||
|
|
||||||
|
bool
|
||||||
|
isnormal(MantissaRange const& range) const noexcept;
|
||||||
|
|
||||||
bool
|
bool
|
||||||
isnormal() const noexcept;
|
isnormal() const noexcept;
|
||||||
|
|
||||||
@@ -516,7 +654,64 @@ private:
|
|||||||
static internalrep
|
static internalrep
|
||||||
externalToInternal(rep mantissa);
|
externalToInternal(rep mantissa);
|
||||||
|
|
||||||
|
/** Breaks down the number into components, potentially de-normalizing it.
|
||||||
|
*
|
||||||
|
* Ensures that the mantissa always has range_.log + 1 digits.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
template <detail::UnsignedMantissa Rep = internalrep>
|
||||||
|
std::tuple<bool, Rep, int>
|
||||||
|
toInternal(MantissaRange const& range) const;
|
||||||
|
|
||||||
|
/** Breaks down the number into components, potentially de-normalizing it.
|
||||||
|
*
|
||||||
|
* Ensures that the mantissa always has range_.log + 1 digits.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
template <detail::UnsignedMantissa Rep = internalrep>
|
||||||
|
std::tuple<bool, Rep, int>
|
||||||
|
toInternal() const;
|
||||||
|
|
||||||
|
/** Rebuilds the number from components.
|
||||||
|
*
|
||||||
|
* If "normalized" is true, the values are expected to be normalized - all
|
||||||
|
* in their valid ranges.
|
||||||
|
*
|
||||||
|
* If "normalized" is false, the values are expected to be "near
|
||||||
|
* normalized", meaning that the mantissa has to be modified at most once to
|
||||||
|
* bring it back into range.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
template <
|
||||||
|
bool expectNormal = true,
|
||||||
|
detail::UnsignedMantissa Rep = internalrep>
|
||||||
|
void
|
||||||
|
fromInternal(
|
||||||
|
bool negative,
|
||||||
|
Rep mantissa,
|
||||||
|
int exponent,
|
||||||
|
MantissaRange const* pRange);
|
||||||
|
|
||||||
|
/** Rebuilds the number from components.
|
||||||
|
*
|
||||||
|
* If "normalized" is true, the values are expected to be normalized - all
|
||||||
|
* in their valid ranges.
|
||||||
|
*
|
||||||
|
* If "normalized" is false, the values are expected to be "near
|
||||||
|
* normalized", meaning that the mantissa has to be modified at most once to
|
||||||
|
* bring it back into range.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
template <
|
||||||
|
bool expectNormal = true,
|
||||||
|
detail::UnsignedMantissa Rep = internalrep>
|
||||||
|
void
|
||||||
|
fromInternal(bool negative, Rep mantissa, int exponent);
|
||||||
|
|
||||||
class Guard;
|
class Guard;
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr static internalrep largestMantissa = largeRange.max;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline constexpr Number::Number(
|
inline constexpr Number::Number(
|
||||||
@@ -524,7 +719,8 @@ inline constexpr Number::Number(
|
|||||||
internalrep mantissa,
|
internalrep mantissa,
|
||||||
int exponent,
|
int exponent,
|
||||||
unchecked) noexcept
|
unchecked) noexcept
|
||||||
: negative_(negative), mantissa_{mantissa}, exponent_{exponent}
|
: mantissa_{(negative ? -1 : 1) * static_cast<rep>(mantissa)}
|
||||||
|
, exponent_{exponent}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,16 +734,6 @@ inline constexpr Number::Number(
|
|||||||
|
|
||||||
constexpr static Number numZero{};
|
constexpr static Number numZero{};
|
||||||
|
|
||||||
inline Number::Number(
|
|
||||||
bool negative,
|
|
||||||
internalrep mantissa,
|
|
||||||
int exponent,
|
|
||||||
normalized)
|
|
||||||
: Number(negative, mantissa, exponent, unchecked{})
|
|
||||||
{
|
|
||||||
normalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Number::Number(internalrep mantissa, int exponent, normalized)
|
inline Number::Number(internalrep mantissa, int exponent, normalized)
|
||||||
: Number(false, mantissa, exponent, normalized{})
|
: Number(false, mantissa, exponent, normalized{})
|
||||||
{
|
{
|
||||||
@@ -570,17 +756,7 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
|
|||||||
inline constexpr Number::rep
|
inline constexpr Number::rep
|
||||||
Number::mantissa() const noexcept
|
Number::mantissa() const noexcept
|
||||||
{
|
{
|
||||||
auto m = mantissa_;
|
return mantissa_;
|
||||||
if (m > maxRep)
|
|
||||||
{
|
|
||||||
XRPL_ASSERT_PARTS(
|
|
||||||
!isnormal() || (m % 10 == 0 && m / 10 <= maxRep),
|
|
||||||
"xrpl::Number::mantissa",
|
|
||||||
"large normalized mantissa has no remainder");
|
|
||||||
m /= 10;
|
|
||||||
}
|
|
||||||
auto const sign = negative_ ? -1 : 1;
|
|
||||||
return sign * static_cast<Number::rep>(m);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the exponent of the external view of the Number.
|
/** Returns the exponent of the external view of the Number.
|
||||||
@@ -591,16 +767,15 @@ Number::mantissa() const noexcept
|
|||||||
inline constexpr int
|
inline constexpr int
|
||||||
Number::exponent() const noexcept
|
Number::exponent() const noexcept
|
||||||
{
|
{
|
||||||
auto e = exponent_;
|
return exponent_;
|
||||||
if (mantissa_ > maxRep)
|
}
|
||||||
{
|
|
||||||
XRPL_ASSERT_PARTS(
|
template <class STAmount, class Asset>
|
||||||
!isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= maxRep),
|
int
|
||||||
"xrpl::Number::exponent",
|
Number::scale(Asset const& asset) const
|
||||||
"large normalized mantissa has no remainder");
|
requires CanUseAsScale<STAmount, Asset>
|
||||||
++e;
|
{
|
||||||
}
|
return STAmount{asset, *this}.exponent();
|
||||||
return e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline constexpr Number
|
inline constexpr Number
|
||||||
@@ -615,7 +790,7 @@ Number::operator-() const noexcept
|
|||||||
if (mantissa_ == 0)
|
if (mantissa_ == 0)
|
||||||
return Number{};
|
return Number{};
|
||||||
auto x = *this;
|
auto x = *this;
|
||||||
x.negative_ = !x.negative_;
|
x.mantissa_ = -1 * x.mantissa_;
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,45 +871,62 @@ Number::min() noexcept
|
|||||||
inline Number
|
inline Number
|
||||||
Number::max() noexcept
|
Number::max() noexcept
|
||||||
{
|
{
|
||||||
return Number{
|
return Number{false, range_.get().max, maxExponent, unchecked{}};
|
||||||
false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Number
|
inline Number
|
||||||
Number::lowest() noexcept
|
Number::lowest() noexcept
|
||||||
{
|
{
|
||||||
return Number{
|
return Number{true, range_.get().max, maxExponent, unchecked{}};
|
||||||
true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
Number::isnormal(MantissaRange const& range) const noexcept
|
||||||
|
{
|
||||||
|
auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_;
|
||||||
|
|
||||||
|
return *this == Number{} ||
|
||||||
|
(range.min <= abs_m && abs_m <= range.max && //
|
||||||
|
minExponent <= exponent_ && exponent_ <= maxExponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
Number::isnormal() const noexcept
|
Number::isnormal() const noexcept
|
||||||
{
|
{
|
||||||
MantissaRange const& range = range_;
|
return isnormal(range_);
|
||||||
auto const abs_m = mantissa_;
|
|
||||||
return *this == Number{} ||
|
|
||||||
(range.min <= abs_m && abs_m <= range.max &&
|
|
||||||
(abs_m <= maxRep || abs_m % 10 == 0) && minExponent <= exponent_ &&
|
|
||||||
exponent_ <= maxExponent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <Integral64 T>
|
template <Integral64 T>
|
||||||
std::pair<T, int>
|
std::pair<T, int>
|
||||||
Number::normalizeToRange(T minMantissa, T maxMantissa) const
|
Number::normalizeToRange(T minMantissa, T maxMantissa) const
|
||||||
{
|
{
|
||||||
bool negative = negative_;
|
bool negative = mantissa_ < 0;
|
||||||
internalrep mantissa = mantissa_;
|
auto const sign = negative ? -1 : 1;
|
||||||
|
internalrep mantissa = sign * mantissa_;
|
||||||
int exponent = exponent_;
|
int exponent = exponent_;
|
||||||
|
|
||||||
if constexpr (std::is_unsigned_v<T>)
|
if constexpr (std::is_unsigned_v<T>)
|
||||||
|
{
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
!negative,
|
!negative,
|
||||||
"xrpl::Number::normalizeToRange",
|
"xrpl::Number::normalizeToRange",
|
||||||
"Number is non-negative for unsigned range.");
|
"Number is non-negative for unsigned range.");
|
||||||
|
// To avoid logical errors in release builds, throw if the Number is
|
||||||
|
// negative for an unsigned range.
|
||||||
|
if (negative)
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Number::normalizeToRange: Number is negative for "
|
||||||
|
"unsigned range.");
|
||||||
|
}
|
||||||
Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa);
|
Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa);
|
||||||
|
|
||||||
auto const sign = negative ? -1 : 1;
|
// Cast mantissa to signed type first (if T is a signed type) to avoid
|
||||||
return std::make_pair(static_cast<T>(sign * mantissa), exponent);
|
// unsigned integer overflow when multiplying by negative sign
|
||||||
|
T signedMantissa = static_cast<T>(mantissa);
|
||||||
|
if (negative)
|
||||||
|
signedMantissa = -signedMantissa;
|
||||||
|
return std::make_pair(signedMantissa, exponent);
|
||||||
|
return std::make_pair(sign * static_cast<T>(mantissa), exponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline constexpr Number
|
inline constexpr Number
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ std::size_t constexpr maxMPTokenMetadataLength = 1024;
|
|||||||
|
|
||||||
/** The maximum amount of MPTokenIssuance */
|
/** The maximum amount of MPTokenIssuance */
|
||||||
std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
|
std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
|
||||||
static_assert(Number::maxRep >= maxMPTokenAmount);
|
static_assert(Number::largestMantissa >= maxMPTokenAmount);
|
||||||
|
|
||||||
/** The maximum length of Data payload */
|
/** The maximum length of Data payload */
|
||||||
std::size_t constexpr maxDataPayloadLength = 256;
|
std::size_t constexpr maxDataPayloadLength = 256;
|
||||||
|
|||||||
@@ -568,6 +568,10 @@ STAmount::fromNumber(A const& a, Number const& number)
|
|||||||
return STAmount{asset, intValue, 0, negative};
|
return STAmount{asset, intValue, 0, negative};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
working.signum() >= 0,
|
||||||
|
"ripple::STAmount::fromNumber",
|
||||||
|
"non-negative Number to normalize");
|
||||||
auto const [mantissa, exponent] =
|
auto const [mantissa, exponent] =
|
||||||
working.normalizeToRange(cMinValue, cMaxValue);
|
working.normalizeToRange(cMinValue, cMaxValue);
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,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);
|
static_assert(INITIAL_XRP.drops() == 100'000'000'000'000'000);
|
||||||
static_assert(Number::maxRep >= INITIAL_XRP.drops());
|
static_assert(Number::largestMantissa >= INITIAL_XRP.drops());
|
||||||
|
|
||||||
/** 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
|
||||||
|
|||||||
@@ -11,18 +11,16 @@
|
|||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#pragma message("Using boost::multiprecision::uint128_t and int128_t")
|
#pragma message("Using boost::multiprecision::uint128_t and int128_t")
|
||||||
#include <boost/multiprecision/cpp_int.hpp>
|
#endif
|
||||||
using uint128_t = boost::multiprecision::uint128_t;
|
|
||||||
using int128_t = boost::multiprecision::int128_t;
|
using uint128_t = ripple::detail::uint128_t;
|
||||||
#else // !defined(_MSC_VER)
|
using int128_t = ripple::detail::int128_t;
|
||||||
using uint128_t = __uint128_t;
|
|
||||||
using int128_t = __int128_t;
|
|
||||||
#endif // !defined(_MSC_VER)
|
|
||||||
|
|
||||||
namespace xrpl {
|
namespace xrpl {
|
||||||
|
|
||||||
@@ -62,10 +60,6 @@ Number::setMantissaScale(MantissaRange::mantissa_scale scale)
|
|||||||
// precision to an operation. This enables the final result
|
// precision to an operation. This enables the final result
|
||||||
// to be correctly rounded to the internal precision of Number.
|
// to be correctly rounded to the internal precision of Number.
|
||||||
|
|
||||||
template <class T>
|
|
||||||
concept UnsignedMantissa =
|
|
||||||
std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>;
|
|
||||||
|
|
||||||
class Number::Guard
|
class Number::Guard
|
||||||
{
|
{
|
||||||
std::uint64_t digits_; // 16 decimal guard digits
|
std::uint64_t digits_; // 16 decimal guard digits
|
||||||
@@ -101,7 +95,7 @@ public:
|
|||||||
round() noexcept;
|
round() noexcept;
|
||||||
|
|
||||||
// Modify the result to the correctly rounded value
|
// Modify the result to the correctly rounded value
|
||||||
template <UnsignedMantissa T>
|
template <detail::UnsignedMantissa T>
|
||||||
void
|
void
|
||||||
doRoundUp(
|
doRoundUp(
|
||||||
bool& negative,
|
bool& negative,
|
||||||
@@ -109,10 +103,10 @@ public:
|
|||||||
int& exponent,
|
int& exponent,
|
||||||
internalrep const& minMantissa,
|
internalrep const& minMantissa,
|
||||||
internalrep const& maxMantissa,
|
internalrep const& maxMantissa,
|
||||||
std::string location);
|
std::string_view location);
|
||||||
|
|
||||||
// Modify the result to the correctly rounded value
|
// Modify the result to the correctly rounded value
|
||||||
template <UnsignedMantissa T>
|
template <detail::UnsignedMantissa T>
|
||||||
void
|
void
|
||||||
doRoundDown(
|
doRoundDown(
|
||||||
bool& negative,
|
bool& negative,
|
||||||
@@ -122,13 +116,13 @@ public:
|
|||||||
|
|
||||||
// Modify the result to the correctly rounded value
|
// Modify the result to the correctly rounded value
|
||||||
void
|
void
|
||||||
doRound(rep& drops, std::string location);
|
doRound(rep& drops, std::string_view location);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void
|
void
|
||||||
doPush(unsigned d) noexcept;
|
doPush(unsigned d) noexcept;
|
||||||
|
|
||||||
template <UnsignedMantissa T>
|
template <detail::UnsignedMantissa T>
|
||||||
void
|
void
|
||||||
bringIntoRange(
|
bringIntoRange(
|
||||||
bool& negative,
|
bool& negative,
|
||||||
@@ -219,7 +213,7 @@ Number::Guard::round() noexcept
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <UnsignedMantissa T>
|
template <detail::UnsignedMantissa T>
|
||||||
void
|
void
|
||||||
Number::Guard::bringIntoRange(
|
Number::Guard::bringIntoRange(
|
||||||
bool& negative,
|
bool& negative,
|
||||||
@@ -238,13 +232,13 @@ Number::Guard::bringIntoRange(
|
|||||||
{
|
{
|
||||||
constexpr Number zero = Number{};
|
constexpr Number zero = Number{};
|
||||||
|
|
||||||
negative = zero.negative_;
|
negative = false;
|
||||||
mantissa = zero.mantissa_;
|
mantissa = zero.mantissa_;
|
||||||
exponent = zero.exponent_;
|
exponent = zero.exponent_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <UnsignedMantissa T>
|
template <detail::UnsignedMantissa T>
|
||||||
void
|
void
|
||||||
Number::Guard::doRoundUp(
|
Number::Guard::doRoundUp(
|
||||||
bool& negative,
|
bool& negative,
|
||||||
@@ -252,7 +246,7 @@ Number::Guard::doRoundUp(
|
|||||||
int& exponent,
|
int& exponent,
|
||||||
internalrep const& minMantissa,
|
internalrep const& minMantissa,
|
||||||
internalrep const& maxMantissa,
|
internalrep const& maxMantissa,
|
||||||
std::string location)
|
std::string_view location)
|
||||||
{
|
{
|
||||||
auto r = round();
|
auto r = round();
|
||||||
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
|
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
|
||||||
@@ -260,7 +254,7 @@ Number::Guard::doRoundUp(
|
|||||||
++mantissa;
|
++mantissa;
|
||||||
// Ensure mantissa after incrementing fits within both the
|
// Ensure mantissa after incrementing fits within both the
|
||||||
// min/maxMantissa range and is a valid "rep".
|
// min/maxMantissa range and is a valid "rep".
|
||||||
if (mantissa > maxMantissa || mantissa > maxRep)
|
if (mantissa > maxMantissa)
|
||||||
{
|
{
|
||||||
mantissa /= 10;
|
mantissa /= 10;
|
||||||
++exponent;
|
++exponent;
|
||||||
@@ -268,10 +262,10 @@ Number::Guard::doRoundUp(
|
|||||||
}
|
}
|
||||||
bringIntoRange(negative, mantissa, exponent, minMantissa);
|
bringIntoRange(negative, mantissa, exponent, minMantissa);
|
||||||
if (exponent > maxExponent)
|
if (exponent > maxExponent)
|
||||||
throw std::overflow_error(location);
|
throw std::overflow_error(std::string{location});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <UnsignedMantissa T>
|
template <detail::UnsignedMantissa T>
|
||||||
void
|
void
|
||||||
Number::Guard::doRoundDown(
|
Number::Guard::doRoundDown(
|
||||||
bool& negative,
|
bool& negative,
|
||||||
@@ -294,12 +288,13 @@ Number::Guard::doRoundDown(
|
|||||||
|
|
||||||
// Modify the result to the correctly rounded value
|
// Modify the result to the correctly rounded value
|
||||||
void
|
void
|
||||||
Number::Guard::doRound(rep& drops, std::string location)
|
Number::Guard::doRound(rep& drops, std::string_view location)
|
||||||
{
|
{
|
||||||
auto r = round();
|
auto r = round();
|
||||||
if (r == 1 || (r == 0 && (drops & 1) == 1))
|
if (r == 1 || (r == 0 && (drops & 1) == 1))
|
||||||
{
|
{
|
||||||
if (drops >= maxRep)
|
auto const& range = range_.get();
|
||||||
|
if (drops >= range.max)
|
||||||
{
|
{
|
||||||
static_assert(sizeof(internalrep) == sizeof(rep));
|
static_assert(sizeof(internalrep) == sizeof(rep));
|
||||||
// This should be impossible, because it's impossible to represent
|
// This should be impossible, because it's impossible to represent
|
||||||
@@ -308,7 +303,7 @@ Number::Guard::doRound(rep& drops, std::string location)
|
|||||||
// or "(maxRep + 1) / 10", neither of which will round up when
|
// or "(maxRep + 1) / 10", neither of which will round up when
|
||||||
// converting to rep, though the latter might overflow _before_
|
// converting to rep, though the latter might overflow _before_
|
||||||
// rounding.
|
// rounding.
|
||||||
throw std::overflow_error(location); // LCOV_EXCL_LINE
|
throw std::overflow_error(std::string{location}); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
++drops;
|
++drops;
|
||||||
}
|
}
|
||||||
@@ -328,17 +323,134 @@ Number::externalToInternal(rep mantissa)
|
|||||||
// If the mantissa is already positive, just return it
|
// If the mantissa is already positive, just return it
|
||||||
if (mantissa >= 0)
|
if (mantissa >= 0)
|
||||||
return mantissa;
|
return mantissa;
|
||||||
// If the mantissa is negative, but fits within the positive range of rep,
|
|
||||||
// return it negated
|
|
||||||
if (mantissa >= -std::numeric_limits<rep>::max())
|
|
||||||
return -mantissa;
|
|
||||||
|
|
||||||
// If the mantissa doesn't fit within the positive range, convert to
|
// Cast to unsigned before negating to avoid undefined behavior
|
||||||
// int128_t, negate that, and cast it back down to the internalrep
|
// when v == INT64_MIN (negating INT64_MIN in signed is UB)
|
||||||
// In practice, this is only going to cover the case of
|
return -static_cast<internalrep>(mantissa);
|
||||||
// std::numeric_limits<rep>::min().
|
}
|
||||||
int128_t temp = mantissa;
|
|
||||||
return static_cast<internalrep>(-temp);
|
/** Breaks down the number into components, potentially de-normalizing it.
|
||||||
|
*
|
||||||
|
* Ensures that the mantissa always has range_.log digits.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
template <detail::UnsignedMantissa Rep>
|
||||||
|
std::tuple<bool, Rep, int>
|
||||||
|
Number::toInternal(MantissaRange const& range) const
|
||||||
|
{
|
||||||
|
auto exponent = exponent_;
|
||||||
|
bool const negative = mantissa_ < 0;
|
||||||
|
auto const sign = negative ? -1 : 1;
|
||||||
|
Rep mantissa = static_cast<Rep>(sign * mantissa_);
|
||||||
|
|
||||||
|
auto const referenceMin = range.referenceMin;
|
||||||
|
auto const minMantissa = range.min;
|
||||||
|
|
||||||
|
if (mantissa != 0 && mantissa >= minMantissa && mantissa < referenceMin)
|
||||||
|
{
|
||||||
|
// Ensure the mantissa has the correct number of digits
|
||||||
|
mantissa *= 10;
|
||||||
|
--exponent;
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
mantissa >= referenceMin && mantissa < referenceMin * 10,
|
||||||
|
"ripple::Number::toInternal()",
|
||||||
|
"Number is within reference range and has 'log' digits");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {negative, mantissa, exponent};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Breaks down the number into components, potentially de-normalizing it.
|
||||||
|
*
|
||||||
|
* Ensures that the mantissa always has range_.log digits.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
template <detail::UnsignedMantissa Rep>
|
||||||
|
std::tuple<bool, Rep, int>
|
||||||
|
Number::toInternal() const
|
||||||
|
{
|
||||||
|
return toInternal(range_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rebuilds the number from components.
|
||||||
|
*
|
||||||
|
* If "normalized" is true, the values are expected to be normalized - all
|
||||||
|
* in their valid ranges.
|
||||||
|
*
|
||||||
|
* If "normalized" is false, the values are expected to be "near
|
||||||
|
* normalized", meaning that the mantissa has to be modified at most once to
|
||||||
|
* bring it back into range.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
template <bool expectNormal, detail::UnsignedMantissa Rep>
|
||||||
|
void
|
||||||
|
Number::fromInternal(
|
||||||
|
bool negative,
|
||||||
|
Rep mantissa,
|
||||||
|
int exponent,
|
||||||
|
MantissaRange const* pRange)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same_v<
|
||||||
|
std::bool_constant<expectNormal>,
|
||||||
|
std::false_type>)
|
||||||
|
{
|
||||||
|
if (!pRange)
|
||||||
|
throw std::runtime_error("Missing range to Number::fromInternal!");
|
||||||
|
auto const& range = *pRange;
|
||||||
|
|
||||||
|
auto const maxMantissa = range.max;
|
||||||
|
auto const minMantissa = range.min;
|
||||||
|
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
mantissa >= minMantissa,
|
||||||
|
"ripple::Number::fromInternal",
|
||||||
|
"mantissa large enough");
|
||||||
|
|
||||||
|
if (mantissa > maxMantissa || mantissa < minMantissa)
|
||||||
|
{
|
||||||
|
normalize(negative, mantissa, exponent, range.min, maxMantissa);
|
||||||
|
}
|
||||||
|
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
mantissa >= minMantissa && mantissa <= maxMantissa,
|
||||||
|
"ripple::Number::fromInternal",
|
||||||
|
"mantissa in range");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const sign = negative ? -1 : 1;
|
||||||
|
|
||||||
|
mantissa_ = sign * static_cast<rep>(mantissa);
|
||||||
|
exponent_ = exponent;
|
||||||
|
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
(pRange && isnormal(*pRange)) || isnormal(),
|
||||||
|
"ripple::Number::fromInternal",
|
||||||
|
"Number is normalized");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rebuilds the number from components.
|
||||||
|
*
|
||||||
|
* If "normalized" is true, the values are expected to be normalized - all in
|
||||||
|
* their valid ranges.
|
||||||
|
*
|
||||||
|
* If "normalized" is false, the values are expected to be "near normalized",
|
||||||
|
* meaning that the mantissa has to be modified at most once to bring it back
|
||||||
|
* into range.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
template <bool expectNormal, detail::UnsignedMantissa Rep>
|
||||||
|
void
|
||||||
|
Number::fromInternal(bool negative, Rep mantissa, int exponent)
|
||||||
|
{
|
||||||
|
MantissaRange const* pRange = nullptr;
|
||||||
|
if constexpr (std::is_same_v<
|
||||||
|
std::bool_constant<expectNormal>,
|
||||||
|
std::false_type>)
|
||||||
|
{
|
||||||
|
pRange = &Number::range_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
fromInternal(negative, mantissa, exponent, pRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr Number
|
constexpr Number
|
||||||
@@ -346,7 +458,7 @@ Number::oneSmall()
|
|||||||
{
|
{
|
||||||
return Number{
|
return Number{
|
||||||
false,
|
false,
|
||||||
Number::smallRange.min,
|
Number::smallRange.referenceMin,
|
||||||
-Number::smallRange.log,
|
-Number::smallRange.log,
|
||||||
Number::unchecked{}};
|
Number::unchecked{}};
|
||||||
};
|
};
|
||||||
@@ -358,7 +470,7 @@ Number::oneLarge()
|
|||||||
{
|
{
|
||||||
return Number{
|
return Number{
|
||||||
false,
|
false,
|
||||||
Number::largeRange.min,
|
Number::largeRange.referenceMin,
|
||||||
-Number::largeRange.log,
|
-Number::largeRange.log,
|
||||||
Number::unchecked{}};
|
Number::unchecked{}};
|
||||||
};
|
};
|
||||||
@@ -366,106 +478,91 @@ Number::oneLarge()
|
|||||||
constexpr Number oneLrg = Number::oneLarge();
|
constexpr Number oneLrg = Number::oneLarge();
|
||||||
|
|
||||||
Number
|
Number
|
||||||
Number::one()
|
Number::one(MantissaRange const& range)
|
||||||
{
|
{
|
||||||
if (&range_.get() == &smallRange)
|
if (&range == &smallRange)
|
||||||
return oneSml;
|
return oneSml;
|
||||||
XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_");
|
XRPL_ASSERT(&range == &largeRange, "Number::one() : valid range");
|
||||||
return oneLrg;
|
return oneLrg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Number
|
||||||
|
Number::one()
|
||||||
|
{
|
||||||
|
return one(range_);
|
||||||
|
}
|
||||||
|
|
||||||
// Use the member names in this static function for now so the diff is cleaner
|
// Use the member names in this static function for now so the diff is cleaner
|
||||||
// TODO: Rename the function parameters to get rid of the "_" suffix
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void
|
void
|
||||||
doNormalize(
|
doNormalize(
|
||||||
bool& negative,
|
bool& negative,
|
||||||
T& mantissa_,
|
T& mantissa,
|
||||||
int& exponent_,
|
int& exponent,
|
||||||
MantissaRange::rep const& minMantissa,
|
MantissaRange::rep const& minMantissa,
|
||||||
MantissaRange::rep const& maxMantissa)
|
MantissaRange::rep const& maxMantissa)
|
||||||
{
|
{
|
||||||
auto constexpr minExponent = Number::minExponent;
|
auto constexpr minExponent = Number::minExponent;
|
||||||
auto constexpr maxExponent = Number::maxExponent;
|
auto constexpr maxExponent = Number::maxExponent;
|
||||||
auto constexpr maxRep = Number::maxRep;
|
|
||||||
|
|
||||||
using Guard = Number::Guard;
|
using Guard = Number::Guard;
|
||||||
|
|
||||||
constexpr Number zero = Number{};
|
constexpr Number zero = Number{};
|
||||||
if (mantissa_ == 0)
|
if (mantissa == 0 || (mantissa < minMantissa && exponent <= minExponent))
|
||||||
{
|
{
|
||||||
mantissa_ = zero.mantissa_;
|
mantissa = zero.mantissa_;
|
||||||
exponent_ = zero.exponent_;
|
exponent = zero.exponent_;
|
||||||
negative = zero.negative_;
|
negative = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto m = mantissa_;
|
|
||||||
while ((m < minMantissa) && (exponent_ > minExponent))
|
auto m = mantissa;
|
||||||
|
while ((m < minMantissa) && (exponent > minExponent))
|
||||||
{
|
{
|
||||||
m *= 10;
|
m *= 10;
|
||||||
--exponent_;
|
--exponent;
|
||||||
}
|
}
|
||||||
Guard g;
|
Guard g;
|
||||||
if (negative)
|
if (negative)
|
||||||
g.set_negative();
|
g.set_negative();
|
||||||
while (m > maxMantissa)
|
while (m > maxMantissa)
|
||||||
{
|
{
|
||||||
if (exponent_ >= maxExponent)
|
if (exponent >= maxExponent)
|
||||||
throw std::overflow_error("Number::normalize 1");
|
throw std::overflow_error("Number::normalize 1");
|
||||||
g.push(m % 10);
|
g.push(m % 10);
|
||||||
m /= 10;
|
m /= 10;
|
||||||
++exponent_;
|
++exponent;
|
||||||
}
|
}
|
||||||
if ((exponent_ < minExponent) || (m < minMantissa))
|
if ((exponent < minExponent) || (m == 0))
|
||||||
{
|
{
|
||||||
mantissa_ = zero.mantissa_;
|
mantissa = zero.mantissa_;
|
||||||
exponent_ = zero.exponent_;
|
exponent = zero.exponent_;
|
||||||
negative = zero.negative_;
|
negative = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When using the largeRange, "m" needs fit within an int64, even if
|
|
||||||
// the final mantissa_ is going to end up larger to fit within the
|
|
||||||
// MantissaRange. Cut it down here so that the rounding will be done while
|
|
||||||
// it's smaller.
|
|
||||||
//
|
|
||||||
// Example: 9,900,000,000,000,123,456 > 9,223,372,036,854,775,807,
|
|
||||||
// so "m" will be modified to 990,000,000,000,012,345. Then that value
|
|
||||||
// will be rounded to 990,000,000,000,012,345 or
|
|
||||||
// 990,000,000,000,012,346, depending on the rounding mode. Finally,
|
|
||||||
// mantissa_ will be "m*10" so it fits within the range, and end up as
|
|
||||||
// 9,900,000,000,000,123,450 or 9,900,000,000,000,123,460.
|
|
||||||
// mantissa() will return mantissa_ / 10, and exponent() will return
|
|
||||||
// exponent_ + 1.
|
|
||||||
if (m > maxRep)
|
|
||||||
{
|
|
||||||
if (exponent_ >= maxExponent)
|
|
||||||
throw std::overflow_error("Number::normalize 1.5");
|
|
||||||
g.push(m % 10);
|
|
||||||
m /= 10;
|
|
||||||
++exponent_;
|
|
||||||
}
|
|
||||||
// Before modification, m should be within the min/max range. After
|
|
||||||
// modification, it must be less than maxRep. In other words, the original
|
|
||||||
// value should have been no more than maxRep * 10.
|
|
||||||
// (maxRep * 10 > maxMantissa)
|
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
m <= maxRep,
|
m <= maxMantissa,
|
||||||
"xrpl::doNormalize",
|
"xrpl::doNormalize",
|
||||||
"intermediate mantissa fits in int64");
|
"intermediate mantissa fits in int64");
|
||||||
mantissa_ = m;
|
mantissa = m;
|
||||||
|
|
||||||
g.doRoundUp(
|
g.doRoundUp(
|
||||||
negative,
|
negative,
|
||||||
mantissa_,
|
mantissa,
|
||||||
exponent_,
|
exponent,
|
||||||
minMantissa,
|
minMantissa,
|
||||||
maxMantissa,
|
maxMantissa,
|
||||||
"Number::normalize 2");
|
"Number::normalize 2");
|
||||||
|
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
mantissa_ >= minMantissa && mantissa_ <= maxMantissa,
|
mantissa >= minMantissa && mantissa <= maxMantissa,
|
||||||
"xrpl::doNormalize",
|
"xrpl::doNormalize",
|
||||||
"final mantissa fits in range");
|
"final mantissa fits in range");
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
exponent >= minExponent && exponent <= maxExponent,
|
||||||
|
"xrpl::doNormalize",
|
||||||
|
"final exponent fits in range");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
@@ -504,11 +601,20 @@ Number::normalize<unsigned long>(
|
|||||||
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa);
|
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Number::normalize(MantissaRange const& range)
|
||||||
|
{
|
||||||
|
auto [negative, mantissa, exponent] = toInternal(range);
|
||||||
|
|
||||||
|
normalize(negative, mantissa, exponent, range.min, range.max);
|
||||||
|
|
||||||
|
fromInternal(negative, mantissa, exponent, &range);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Number::normalize()
|
Number::normalize()
|
||||||
{
|
{
|
||||||
auto const& range = range_.get();
|
normalize(range_);
|
||||||
normalize(negative_, mantissa_, exponent_, range.min, range.max);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
|
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
|
||||||
@@ -518,24 +624,33 @@ Number
|
|||||||
Number::shiftExponent(int exponentDelta) const
|
Number::shiftExponent(int exponentDelta) const
|
||||||
{
|
{
|
||||||
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::shiftExponent", "normalized");
|
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::shiftExponent", "normalized");
|
||||||
auto const newExponent = exponent_ + exponentDelta;
|
|
||||||
if (newExponent >= maxExponent)
|
Number result = *this;
|
||||||
|
|
||||||
|
result.exponent_ += exponentDelta;
|
||||||
|
|
||||||
|
if (result.exponent_ >= maxExponent)
|
||||||
throw std::overflow_error("Number::shiftExponent");
|
throw std::overflow_error("Number::shiftExponent");
|
||||||
if (newExponent < minExponent)
|
if (result.exponent_ < minExponent)
|
||||||
{
|
{
|
||||||
return Number{};
|
return Number{};
|
||||||
}
|
}
|
||||||
Number const result{negative_, mantissa_, newExponent, unchecked{}};
|
|
||||||
XRPL_ASSERT_PARTS(
|
|
||||||
result.isnormal(),
|
|
||||||
"xrpl::Number::shiftExponent",
|
|
||||||
"result is normalized");
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
|
||||||
|
{
|
||||||
|
auto const& range = range_.get();
|
||||||
|
normalize(negative, mantissa, exponent, range.min, range.max);
|
||||||
|
fromInternal(negative, mantissa, exponent, &range);
|
||||||
|
}
|
||||||
|
|
||||||
Number&
|
Number&
|
||||||
Number::operator+=(Number const& y)
|
Number::operator+=(Number const& y)
|
||||||
{
|
{
|
||||||
|
auto const& range = range_.get();
|
||||||
|
|
||||||
constexpr Number zero = Number{};
|
constexpr Number zero = Number{};
|
||||||
if (y == zero)
|
if (y == zero)
|
||||||
return *this;
|
return *this;
|
||||||
@@ -551,7 +666,7 @@ Number::operator+=(Number const& y)
|
|||||||
}
|
}
|
||||||
|
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
isnormal() && y.isnormal(),
|
isnormal(range) && y.isnormal(range),
|
||||||
"xrpl::Number::operator+=(Number) : is normal");
|
"xrpl::Number::operator+=(Number) : is normal");
|
||||||
// *n = negative
|
// *n = negative
|
||||||
// *s = sign
|
// *s = sign
|
||||||
@@ -560,13 +675,10 @@ Number::operator+=(Number const& y)
|
|||||||
|
|
||||||
// Need to use uint128_t, because large mantissas can overflow when added
|
// Need to use uint128_t, because large mantissas can overflow when added
|
||||||
// together.
|
// together.
|
||||||
bool xn = negative_;
|
auto [xn, xm, xe] = toInternal<uint128_t>(range);
|
||||||
uint128_t xm = mantissa_;
|
|
||||||
auto xe = exponent_;
|
auto [yn, ym, ye] = y.toInternal<uint128_t>(range);
|
||||||
|
|
||||||
bool yn = y.negative_;
|
|
||||||
uint128_t ym = y.mantissa_;
|
|
||||||
auto ye = y.exponent_;
|
|
||||||
Guard g;
|
Guard g;
|
||||||
if (xe < ye)
|
if (xe < ye)
|
||||||
{
|
{
|
||||||
@@ -591,14 +703,13 @@ Number::operator+=(Number const& y)
|
|||||||
} while (xe > ye);
|
} while (xe > ye);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const& range = range_.get();
|
|
||||||
auto const& minMantissa = range.min;
|
auto const& minMantissa = range.min;
|
||||||
auto const& maxMantissa = range.max;
|
auto const& maxMantissa = range.max;
|
||||||
|
|
||||||
if (xn == yn)
|
if (xn == yn)
|
||||||
{
|
{
|
||||||
xm += ym;
|
xm += ym;
|
||||||
if (xm > maxMantissa || xm > maxRep)
|
if (xm > maxMantissa)
|
||||||
{
|
{
|
||||||
g.push(xm % 10);
|
g.push(xm % 10);
|
||||||
xm /= 10;
|
xm /= 10;
|
||||||
@@ -619,7 +730,7 @@ Number::operator+=(Number const& y)
|
|||||||
xe = ye;
|
xe = ye;
|
||||||
xn = yn;
|
xn = yn;
|
||||||
}
|
}
|
||||||
while (xm < minMantissa && xm * 10 <= maxRep)
|
while (xm < minMantissa)
|
||||||
{
|
{
|
||||||
xm *= 10;
|
xm *= 10;
|
||||||
xm -= g.pop();
|
xm -= g.pop();
|
||||||
@@ -628,10 +739,8 @@ Number::operator+=(Number const& y)
|
|||||||
g.doRoundDown(xn, xm, xe, minMantissa);
|
g.doRoundDown(xn, xm, xe, minMantissa);
|
||||||
}
|
}
|
||||||
|
|
||||||
negative_ = xn;
|
normalize(xn, xm, xe, minMantissa, maxMantissa);
|
||||||
mantissa_ = static_cast<internalrep>(xm);
|
fromInternal(xn, xm, xe, &range);
|
||||||
exponent_ = xe;
|
|
||||||
normalize();
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,6 +775,8 @@ divu10(uint128_t& u)
|
|||||||
Number&
|
Number&
|
||||||
Number::operator*=(Number const& y)
|
Number::operator*=(Number const& y)
|
||||||
{
|
{
|
||||||
|
auto const& range = range_.get();
|
||||||
|
|
||||||
constexpr Number zero = Number{};
|
constexpr Number zero = Number{};
|
||||||
if (*this == zero)
|
if (*this == zero)
|
||||||
return *this;
|
return *this;
|
||||||
@@ -679,15 +790,11 @@ Number::operator*=(Number const& y)
|
|||||||
// *m = mantissa
|
// *m = mantissa
|
||||||
// *e = exponent
|
// *e = exponent
|
||||||
|
|
||||||
bool xn = negative_;
|
auto [xn, xm, xe] = toInternal(range);
|
||||||
int xs = xn ? -1 : 1;
|
int xs = xn ? -1 : 1;
|
||||||
internalrep xm = mantissa_;
|
|
||||||
auto xe = exponent_;
|
|
||||||
|
|
||||||
bool yn = y.negative_;
|
auto [yn, ym, ye] = y.toInternal(range);
|
||||||
int ys = yn ? -1 : 1;
|
int ys = yn ? -1 : 1;
|
||||||
internalrep ym = y.mantissa_;
|
|
||||||
auto ye = y.exponent_;
|
|
||||||
|
|
||||||
auto zm = uint128_t(xm) * uint128_t(ym);
|
auto zm = uint128_t(xm) * uint128_t(ym);
|
||||||
auto ze = xe + ye;
|
auto ze = xe + ye;
|
||||||
@@ -697,11 +804,10 @@ Number::operator*=(Number const& y)
|
|||||||
if (zn)
|
if (zn)
|
||||||
g.set_negative();
|
g.set_negative();
|
||||||
|
|
||||||
auto const& range = range_.get();
|
|
||||||
auto const& minMantissa = range.min;
|
auto const& minMantissa = range.min;
|
||||||
auto const& maxMantissa = range.max;
|
auto const& maxMantissa = range.max;
|
||||||
|
|
||||||
while (zm > maxMantissa || zm > maxRep)
|
while (zm > maxMantissa)
|
||||||
{
|
{
|
||||||
// The following is optimization for:
|
// The following is optimization for:
|
||||||
// g.push(static_cast<unsigned>(zm % 10));
|
// g.push(static_cast<unsigned>(zm % 10));
|
||||||
@@ -718,17 +824,17 @@ Number::operator*=(Number const& y)
|
|||||||
minMantissa,
|
minMantissa,
|
||||||
maxMantissa,
|
maxMantissa,
|
||||||
"Number::multiplication overflow : exponent is " + std::to_string(xe));
|
"Number::multiplication overflow : exponent is " + std::to_string(xe));
|
||||||
negative_ = zn;
|
|
||||||
mantissa_ = xm;
|
|
||||||
exponent_ = xe;
|
|
||||||
|
|
||||||
normalize();
|
normalize(zn, xm, xe, minMantissa, maxMantissa);
|
||||||
|
fromInternal(zn, xm, xe, &range);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Number&
|
Number&
|
||||||
Number::operator/=(Number const& y)
|
Number::operator/=(Number const& y)
|
||||||
{
|
{
|
||||||
|
auto const& range = range_.get();
|
||||||
|
|
||||||
constexpr Number zero = Number{};
|
constexpr Number zero = Number{};
|
||||||
if (y == zero)
|
if (y == zero)
|
||||||
throw std::overflow_error("Number: divide by 0");
|
throw std::overflow_error("Number: divide by 0");
|
||||||
@@ -741,17 +847,12 @@ Number::operator/=(Number const& y)
|
|||||||
// *m = mantissa
|
// *m = mantissa
|
||||||
// *e = exponent
|
// *e = exponent
|
||||||
|
|
||||||
bool np = negative_;
|
auto [np, nm, ne] = toInternal(range);
|
||||||
int ns = (np ? -1 : 1);
|
int ns = (np ? -1 : 1);
|
||||||
auto nm = mantissa_;
|
|
||||||
auto ne = exponent_;
|
|
||||||
|
|
||||||
bool dp = y.negative_;
|
auto [dp, dm, de] = y.toInternal(range);
|
||||||
int ds = (dp ? -1 : 1);
|
int ds = (dp ? -1 : 1);
|
||||||
auto dm = y.mantissa_;
|
|
||||||
auto de = y.exponent_;
|
|
||||||
|
|
||||||
auto const& range = range_.get();
|
|
||||||
auto const& minMantissa = range.min;
|
auto const& minMantissa = range.min;
|
||||||
auto const& maxMantissa = range.max;
|
auto const& maxMantissa = range.max;
|
||||||
|
|
||||||
@@ -763,7 +864,7 @@ Number::operator/=(Number const& y)
|
|||||||
// f can be up to 10^(38-19) = 10^19 safely
|
// f can be up to 10^(38-19) = 10^19 safely
|
||||||
static_assert(smallRange.log == 15);
|
static_assert(smallRange.log == 15);
|
||||||
static_assert(largeRange.log == 18);
|
static_assert(largeRange.log == 18);
|
||||||
bool small = Number::getMantissaScale() == MantissaRange::small;
|
bool small = range.scale == MantissaRange::small;
|
||||||
uint128_t const f =
|
uint128_t const f =
|
||||||
small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
|
small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
@@ -815,11 +916,9 @@ Number::operator/=(Number const& y)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
normalize(zn, zm, ze, minMantissa, maxMantissa);
|
normalize(zn, zm, ze, minMantissa, maxMantissa);
|
||||||
negative_ = zn;
|
fromInternal(zn, zm, ze, &range);
|
||||||
mantissa_ = static_cast<internalrep>(zm);
|
|
||||||
exponent_ = ze;
|
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
isnormal(), "xrpl::Number::operator/=", "result is normalized");
|
isnormal(range), "xrpl::Number::operator/=", "result is normalized");
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -831,7 +930,7 @@ Number::operator rep() const
|
|||||||
Guard g;
|
Guard g;
|
||||||
if (drops != 0)
|
if (drops != 0)
|
||||||
{
|
{
|
||||||
if (negative_)
|
if (drops < 0)
|
||||||
{
|
{
|
||||||
g.set_negative();
|
g.set_negative();
|
||||||
drops = -drops;
|
drops = -drops;
|
||||||
@@ -843,7 +942,7 @@ Number::operator rep() const
|
|||||||
}
|
}
|
||||||
for (; offset > 0; --offset)
|
for (; offset > 0; --offset)
|
||||||
{
|
{
|
||||||
if (drops > maxRep / 10)
|
if (drops >= largeRange.min)
|
||||||
throw std::overflow_error("Number::operator rep() overflow");
|
throw std::overflow_error("Number::operator rep() overflow");
|
||||||
drops *= 10;
|
drops *= 10;
|
||||||
}
|
}
|
||||||
@@ -873,20 +972,22 @@ Number::truncate() const noexcept
|
|||||||
std::string
|
std::string
|
||||||
to_string(Number const& amount)
|
to_string(Number const& amount)
|
||||||
{
|
{
|
||||||
|
auto const& range = Number::range_.get();
|
||||||
|
|
||||||
// keep full internal accuracy, but make more human friendly if possible
|
// keep full internal accuracy, but make more human friendly if possible
|
||||||
constexpr Number zero = Number{};
|
constexpr Number zero = Number{};
|
||||||
if (amount == zero)
|
if (amount == zero)
|
||||||
return "0";
|
return "0";
|
||||||
|
|
||||||
auto exponent = amount.exponent_;
|
// The mantissa must have a set number of decimal places for this to work
|
||||||
auto mantissa = amount.mantissa_;
|
auto [negative, mantissa, exponent] = amount.toInternal(range);
|
||||||
bool const negative = amount.negative_;
|
|
||||||
|
|
||||||
// Use scientific notation for exponents that are too small or too large
|
// Use scientific notation for exponents that are too small or too large
|
||||||
auto const rangeLog = Number::mantissaLog();
|
auto const rangeLog = range.log;
|
||||||
if (((exponent != 0) &&
|
if (((exponent != 0 && amount.exponent() != 0) &&
|
||||||
((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
|
((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
|
||||||
{
|
{
|
||||||
|
// Remove trailing zeroes from the mantissa.
|
||||||
while (mantissa != 0 && mantissa % 10 == 0 &&
|
while (mantissa != 0 && mantissa % 10 == 0 &&
|
||||||
exponent < Number::maxExponent)
|
exponent < Number::maxExponent)
|
||||||
{
|
{
|
||||||
@@ -895,8 +996,11 @@ to_string(Number const& amount)
|
|||||||
}
|
}
|
||||||
std::string ret = negative ? "-" : "";
|
std::string ret = negative ? "-" : "";
|
||||||
ret.append(std::to_string(mantissa));
|
ret.append(std::to_string(mantissa));
|
||||||
ret.append(1, 'e');
|
if (exponent != 0)
|
||||||
ret.append(std::to_string(exponent));
|
{
|
||||||
|
ret.append(1, 'e');
|
||||||
|
ret.append(std::to_string(exponent));
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -984,20 +1088,11 @@ power(Number const& f, unsigned n)
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns f^(1/d)
|
|
||||||
// Uses Newton–Raphson iterations until the result stops changing
|
|
||||||
// to find the non-negative root of the polynomial g(x) = x^d - f
|
|
||||||
|
|
||||||
// This function, and power(Number f, unsigned n, unsigned d)
|
|
||||||
// treat corner cases such as 0 roots as advised by Annex F of
|
|
||||||
// the C standard, which itself is consistent with the IEEE
|
|
||||||
// floating point standards.
|
|
||||||
|
|
||||||
Number
|
Number
|
||||||
root(Number f, unsigned d)
|
Number::root(MantissaRange const& range, Number f, unsigned d)
|
||||||
{
|
{
|
||||||
constexpr Number zero = Number{};
|
constexpr Number zero = Number{};
|
||||||
auto const one = Number::one();
|
auto const one = Number::one(range);
|
||||||
|
|
||||||
if (f == one || d == 1)
|
if (f == one || d == 1)
|
||||||
return f;
|
return f;
|
||||||
@@ -1014,22 +1109,30 @@ root(Number f, unsigned d)
|
|||||||
if (f == zero)
|
if (f == zero)
|
||||||
return f;
|
return f;
|
||||||
|
|
||||||
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
|
auto const [e, di] = [&]() {
|
||||||
auto e = f.exponent_ + Number::mantissaLog() + 1;
|
auto const [negative, mantissa, exponent] = f.toInternal(range);
|
||||||
auto const di = static_cast<int>(d);
|
|
||||||
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
|
// Scale f into the range (0, 1) such that the scale change (e) is a
|
||||||
{
|
// multiple of the root (d)
|
||||||
int k = (e >= 0 ? e : e - (di - 1)) / di;
|
auto e = exponent + range.log + 1;
|
||||||
int k2 = e - k * di;
|
auto const di = static_cast<int>(d);
|
||||||
if (k2 == 0)
|
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
|
||||||
return 0;
|
{
|
||||||
return di - k2;
|
int k = (e >= 0 ? e : e - (di - 1)) / di;
|
||||||
|
int k2 = e - k * di;
|
||||||
|
if (k2 == 0)
|
||||||
|
return 0;
|
||||||
|
return di - k2;
|
||||||
|
}();
|
||||||
|
e += ex;
|
||||||
|
f = f.shiftExponent(-e); // f /= 10^e;
|
||||||
|
return std::make_tuple(e, di);
|
||||||
}();
|
}();
|
||||||
e += ex;
|
|
||||||
f = f.shiftExponent(-e); // f /= 10^e;
|
|
||||||
|
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
f.isnormal(), "xrpl::root(Number, unsigned)", "f is normalized");
|
e % di == 0, "xrpl::root(Number, unsigned)", "e is divisible by d");
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
f.isnormal(range), "xrpl::root(Number, unsigned)", "f is normalized");
|
||||||
bool neg = false;
|
bool neg = false;
|
||||||
if (f < zero)
|
if (f < zero)
|
||||||
{
|
{
|
||||||
@@ -1063,17 +1166,34 @@ root(Number f, unsigned d)
|
|||||||
// return r * 10^(e/d) to reverse scaling
|
// return r * 10^(e/d) to reverse scaling
|
||||||
auto const result = r.shiftExponent(e / di);
|
auto const result = r.shiftExponent(e / di);
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
result.isnormal(),
|
result.isnormal(range),
|
||||||
"xrpl::root(Number, unsigned)",
|
"xrpl::root(Number, unsigned)",
|
||||||
"result is normalized");
|
"result is normalized");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns f^(1/d)
|
||||||
|
// Uses Newton–Raphson iterations until the result stops changing
|
||||||
|
// to find the non-negative root of the polynomial g(x) = x^d - f
|
||||||
|
|
||||||
|
// This function, and power(Number f, unsigned n, unsigned d)
|
||||||
|
// treat corner cases such as 0 roots as advised by Annex F of
|
||||||
|
// the C standard, which itself is consistent with the IEEE
|
||||||
|
// floating point standards.
|
||||||
|
|
||||||
|
Number
|
||||||
|
root(Number f, unsigned d)
|
||||||
|
{
|
||||||
|
auto const& range = Number::range_.get();
|
||||||
|
return Number::root(range, f, d);
|
||||||
|
}
|
||||||
|
|
||||||
Number
|
Number
|
||||||
root2(Number f)
|
root2(Number f)
|
||||||
{
|
{
|
||||||
|
auto const& range = Number::range_.get();
|
||||||
constexpr Number zero = Number{};
|
constexpr Number zero = Number{};
|
||||||
auto const one = Number::one();
|
auto const one = Number::one(range);
|
||||||
|
|
||||||
if (f == one)
|
if (f == one)
|
||||||
return f;
|
return f;
|
||||||
@@ -1082,12 +1202,19 @@ root2(Number f)
|
|||||||
if (f == zero)
|
if (f == zero)
|
||||||
return f;
|
return f;
|
||||||
|
|
||||||
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
|
auto const e = [&]() {
|
||||||
auto e = f.exponent_ + Number::mantissaLog() + 1;
|
auto const [negative, mantissa, exponent] = f.toInternal(range);
|
||||||
if (e % 2 != 0)
|
|
||||||
++e;
|
// Scale f into the range (0, 1) such that f's exponent is a
|
||||||
f = f.shiftExponent(-e); // f /= 10^e;
|
// multiple of d
|
||||||
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root2(Number)", "f is normalized");
|
auto e = exponent + range.log + 1;
|
||||||
|
if (e % 2 != 0)
|
||||||
|
++e;
|
||||||
|
f = f.shiftExponent(-e); // f /= 10^e;
|
||||||
|
return e;
|
||||||
|
}();
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
f.isnormal(range), "xrpl::root2(Number)", "f is normalized");
|
||||||
|
|
||||||
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
|
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
|
||||||
auto const D = 105;
|
auto const D = 105;
|
||||||
@@ -1110,7 +1237,7 @@ root2(Number f)
|
|||||||
// return r * 10^(e/2) to reverse scaling
|
// return r * 10^(e/2) to reverse scaling
|
||||||
auto const result = r.shiftExponent(e / 2);
|
auto const result = r.shiftExponent(e / 2);
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
result.isnormal(), "xrpl::root2(Number)", "result is normalized");
|
result.isnormal(range), "xrpl::root2(Number)", "result is normalized");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1120,8 +1247,10 @@ root2(Number f)
|
|||||||
Number
|
Number
|
||||||
power(Number const& f, unsigned n, unsigned d)
|
power(Number const& f, unsigned n, unsigned d)
|
||||||
{
|
{
|
||||||
|
auto const& range = Number::range_.get();
|
||||||
|
|
||||||
constexpr Number zero = Number{};
|
constexpr Number zero = Number{};
|
||||||
auto const one = Number::one();
|
auto const one = Number::one(range);
|
||||||
|
|
||||||
if (f == one)
|
if (f == one)
|
||||||
return f;
|
return f;
|
||||||
@@ -1143,7 +1272,7 @@ power(Number const& f, unsigned n, unsigned d)
|
|||||||
d /= g;
|
d /= g;
|
||||||
if ((n % 2) == 1 && (d % 2) == 0 && f < zero)
|
if ((n % 2) == 1 && (d % 2) == 0 && f < zero)
|
||||||
throw std::overflow_error("Number::power nan");
|
throw std::overflow_error("Number::power nan");
|
||||||
return root(power(f, n), d);
|
return Number::root(range, power(f, n), d);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace xrpl
|
} // namespace xrpl
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <xrpld/app/tx/apply.h>
|
#include <xrpld/app/tx/apply.h>
|
||||||
#include <xrpld/app/tx/detail/ApplyContext.h>
|
#include <xrpld/app/tx/detail/ApplyContext.h>
|
||||||
|
#include <xrpld/app/tx/detail/InvariantCheck.h>
|
||||||
|
|
||||||
#include <xrpl/beast/unit_test/suite.h>
|
#include <xrpl/beast/unit_test/suite.h>
|
||||||
#include <xrpl/beast/utility/Journal.h>
|
#include <xrpl/beast/utility/Journal.h>
|
||||||
@@ -20,6 +21,9 @@
|
|||||||
|
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace xrpl {
|
namespace xrpl {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
@@ -3888,6 +3892,140 @@ class Invariants_test : public beast::unit_test::suite
|
|||||||
precloseMpt);
|
precloseMpt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testVaultComputeMinScale()
|
||||||
|
{
|
||||||
|
using namespace jtx;
|
||||||
|
|
||||||
|
Account const issuer{"issuer"};
|
||||||
|
PrettyAsset const vaultAsset = issuer["IOU"];
|
||||||
|
|
||||||
|
struct TestCase
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::int32_t expectedMinScale;
|
||||||
|
std::initializer_list<ValidVault::DeltaInfo const> values;
|
||||||
|
};
|
||||||
|
|
||||||
|
NumberMantissaScaleGuard g{MantissaRange::large};
|
||||||
|
|
||||||
|
auto makeDelta =
|
||||||
|
[&vaultAsset](Number const& n) -> ValidVault::DeltaInfo {
|
||||||
|
return {n, n.scale<STAmount>(vaultAsset.raw())};
|
||||||
|
};
|
||||||
|
|
||||||
|
auto const testCases = std::vector<TestCase>{
|
||||||
|
{
|
||||||
|
.name = "No values",
|
||||||
|
.expectedMinScale = 0,
|
||||||
|
.values = {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "Mixed integer and Number values",
|
||||||
|
.expectedMinScale = -15,
|
||||||
|
.values =
|
||||||
|
{makeDelta(1), makeDelta(-1), makeDelta(Number{10, -1})},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "Mixed scales",
|
||||||
|
.expectedMinScale = -17,
|
||||||
|
.values =
|
||||||
|
{makeDelta(Number{1, -2}),
|
||||||
|
makeDelta(Number{5, -3}),
|
||||||
|
makeDelta(Number{3, -2})},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "Equal scales",
|
||||||
|
.expectedMinScale = -16,
|
||||||
|
.values =
|
||||||
|
{makeDelta(Number{1, -1}),
|
||||||
|
makeDelta(Number{5, -1}),
|
||||||
|
makeDelta(Number{1, -1})},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "Mixed mantissa sizes",
|
||||||
|
.expectedMinScale = -12,
|
||||||
|
.values =
|
||||||
|
{makeDelta(Number{1}),
|
||||||
|
makeDelta(Number{1234, -3}),
|
||||||
|
makeDelta(Number{12345, -6}),
|
||||||
|
makeDelta(Number{123, 1})},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto const& tc : testCases)
|
||||||
|
{
|
||||||
|
testcase("vault computeMinScale: " + tc.name);
|
||||||
|
|
||||||
|
auto const actualScale =
|
||||||
|
ValidVault::computeMinScale(vaultAsset, tc.values);
|
||||||
|
|
||||||
|
BEAST_EXPECTS(
|
||||||
|
actualScale == tc.expectedMinScale,
|
||||||
|
"expected: " + std::to_string(tc.expectedMinScale) +
|
||||||
|
", actual: " + std::to_string(actualScale));
|
||||||
|
for (auto const& num : tc.values)
|
||||||
|
{
|
||||||
|
// None of these scales are far enough apart that rounding the
|
||||||
|
// values would lose information, so check that the rounded
|
||||||
|
// value matches the original.
|
||||||
|
auto const actualRounded =
|
||||||
|
roundToAsset(vaultAsset, num.delta, actualScale);
|
||||||
|
BEAST_EXPECTS(
|
||||||
|
actualRounded == num.delta,
|
||||||
|
"number " + to_string(num.delta) + " rounded to scale " +
|
||||||
|
std::to_string(actualScale) + " is " +
|
||||||
|
to_string(actualRounded));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const testCases2 = std::vector<TestCase>{
|
||||||
|
{
|
||||||
|
.name = "False equivalence",
|
||||||
|
.expectedMinScale = -15,
|
||||||
|
.values =
|
||||||
|
{
|
||||||
|
makeDelta(Number{1234567890123456789, -18}),
|
||||||
|
makeDelta(Number{12345, -4}),
|
||||||
|
makeDelta(Number{1}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Unlike the first set of test cases, the values in these test could
|
||||||
|
// look equivalent if using the wrong scale.
|
||||||
|
for (auto const& tc : testCases2)
|
||||||
|
{
|
||||||
|
testcase("vault computeMinScale: " + tc.name);
|
||||||
|
|
||||||
|
auto const actualScale =
|
||||||
|
ValidVault::computeMinScale(vaultAsset, tc.values);
|
||||||
|
|
||||||
|
BEAST_EXPECTS(
|
||||||
|
actualScale == tc.expectedMinScale,
|
||||||
|
"expected: " + std::to_string(tc.expectedMinScale) +
|
||||||
|
", actual: " + std::to_string(actualScale));
|
||||||
|
std::optional<Number> first;
|
||||||
|
Number firstRounded;
|
||||||
|
for (auto const& num : tc.values)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
first = num.delta;
|
||||||
|
firstRounded =
|
||||||
|
roundToAsset(vaultAsset, num.delta, actualScale);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto const numRounded =
|
||||||
|
roundToAsset(vaultAsset, num.delta, actualScale);
|
||||||
|
BEAST_EXPECTS(
|
||||||
|
numRounded != firstRounded,
|
||||||
|
"at a scale of " + std::to_string(actualScale) + " " +
|
||||||
|
to_string(num.delta) + " == " + to_string(*first));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void
|
void
|
||||||
run() override
|
run() override
|
||||||
@@ -3911,6 +4049,7 @@ public:
|
|||||||
testValidPseudoAccounts();
|
testValidPseudoAccounts();
|
||||||
testValidLoanBroker();
|
testValidLoanBroker();
|
||||||
testVault();
|
testVault();
|
||||||
|
testVaultComputeMinScale();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,11 @@
|
|||||||
#include <test/jtx.h>
|
#include <test/jtx.h>
|
||||||
#include <test/jtx/Account.h>
|
#include <test/jtx/Account.h>
|
||||||
#include <test/jtx/amount.h>
|
#include <test/jtx/amount.h>
|
||||||
#include <test/jtx/mpt.h>
|
|
||||||
|
|
||||||
#include <xrpld/app/misc/LendingHelpers.h>
|
#include <xrpld/app/misc/LendingHelpers.h>
|
||||||
#include <xrpld/app/misc/LoadFeeTrack.h>
|
|
||||||
#include <xrpld/app/tx/detail/Batch.h>
|
#include <xrpld/app/tx/detail/Batch.h>
|
||||||
#include <xrpld/app/tx/detail/LoanSet.h>
|
#include <xrpld/app/tx/detail/LoanSet.h>
|
||||||
|
|
||||||
#include <xrpl/beast/xor_shift_engine.h>
|
|
||||||
#include <xrpl/protocol/SField.h>
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|||||||
@@ -7641,6 +7641,149 @@ protected:
|
|||||||
BEAST_EXPECT(afterSecondCoverAvailable == 0);
|
BEAST_EXPECT(afterSecondCoverAvailable == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that vault withdrawals work correctly when the vault has unrealized
|
||||||
|
// loss from an impaired loan, ensuring the invariant check properly
|
||||||
|
// accounts for the loss.
|
||||||
|
void
|
||||||
|
testWithdrawReflectsUnrealizedLoss()
|
||||||
|
{
|
||||||
|
using namespace jtx;
|
||||||
|
using namespace loan;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
testcase("Vault withdraw reflects sfLossUnrealized");
|
||||||
|
|
||||||
|
// Test constants
|
||||||
|
static constexpr std::int64_t INITIAL_FUNDING = 1'000'000;
|
||||||
|
static constexpr std::int64_t LENDER_INITIAL_IOU = 5'000'000;
|
||||||
|
static constexpr std::int64_t DEPOSITOR_INITIAL_IOU = 1'000'000;
|
||||||
|
static constexpr std::int64_t BORROWER_INITIAL_IOU = 100'000;
|
||||||
|
static constexpr std::int64_t DEPOSIT_AMOUNT = 5'000;
|
||||||
|
static constexpr std::int64_t PRINCIPAL_AMOUNT = 99;
|
||||||
|
static constexpr std::uint64_t EXPECTED_SHARES_PER_DEPOSITOR =
|
||||||
|
5'000'000'000;
|
||||||
|
static constexpr std::uint32_t PAYMENT_INTERVAL = 600;
|
||||||
|
static constexpr std::uint32_t PAYMENT_TOTAL = 2;
|
||||||
|
|
||||||
|
Env env(*this, all);
|
||||||
|
|
||||||
|
// Setup accounts
|
||||||
|
Account const issuer{"issuer"};
|
||||||
|
Account const lender{"lender"};
|
||||||
|
Account const depositorA{"lpA"};
|
||||||
|
Account const depositorB{"lpB"};
|
||||||
|
Account const borrower{"borrowerA"};
|
||||||
|
|
||||||
|
env.fund(
|
||||||
|
XRP(INITIAL_FUNDING),
|
||||||
|
issuer,
|
||||||
|
lender,
|
||||||
|
depositorA,
|
||||||
|
depositorB,
|
||||||
|
borrower);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Setup trust lines
|
||||||
|
PrettyAsset const iouAsset = issuer[iouCurrency];
|
||||||
|
env(trust(lender, iouAsset(10'000'000)));
|
||||||
|
env(trust(depositorA, iouAsset(10'000'000)));
|
||||||
|
env(trust(depositorB, iouAsset(10'000'000)));
|
||||||
|
env(trust(borrower, iouAsset(10'000'000)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Fund accounts with IOUs
|
||||||
|
env(pay(issuer, lender, iouAsset(LENDER_INITIAL_IOU)));
|
||||||
|
env(pay(issuer, depositorA, iouAsset(DEPOSITOR_INITIAL_IOU)));
|
||||||
|
env(pay(issuer, depositorB, iouAsset(DEPOSITOR_INITIAL_IOU)));
|
||||||
|
env(pay(issuer, borrower, iouAsset(BORROWER_INITIAL_IOU)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Create vault and broker, then add deposits from two depositors
|
||||||
|
auto const broker = createVaultAndBroker(env, iouAsset, lender);
|
||||||
|
Vault v{env};
|
||||||
|
|
||||||
|
env(v.deposit({
|
||||||
|
.depositor = depositorA,
|
||||||
|
.id = broker.vaultKeylet().key,
|
||||||
|
.amount = iouAsset(DEPOSIT_AMOUNT),
|
||||||
|
}),
|
||||||
|
ter(tesSUCCESS));
|
||||||
|
env(v.deposit({
|
||||||
|
.depositor = depositorB,
|
||||||
|
.id = broker.vaultKeylet().key,
|
||||||
|
.amount = iouAsset(DEPOSIT_AMOUNT),
|
||||||
|
}),
|
||||||
|
ter(tesSUCCESS));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Create a loan
|
||||||
|
auto const sleBroker = env.le(keylet::loanbroker(broker.brokerID));
|
||||||
|
if (!BEAST_EXPECT(sleBroker))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto const loanKeylet =
|
||||||
|
keylet::loan(broker.brokerID, sleBroker->at(sfLoanSequence));
|
||||||
|
|
||||||
|
env(set(borrower, broker.brokerID, PRINCIPAL_AMOUNT),
|
||||||
|
sig(sfCounterpartySignature, lender),
|
||||||
|
paymentTotal(PAYMENT_TOTAL),
|
||||||
|
paymentInterval(PAYMENT_INTERVAL),
|
||||||
|
fee(env.current()->fees().base * 2),
|
||||||
|
ter(tesSUCCESS));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Impair the loan to create unrealized loss
|
||||||
|
env(manage(lender, loanKeylet.key, tfLoanImpair), ter(tesSUCCESS));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Verify unrealized loss is recorded in the vault
|
||||||
|
auto const vaultAfterImpair = env.le(broker.vaultKeylet());
|
||||||
|
if (!BEAST_EXPECT(vaultAfterImpair))
|
||||||
|
return;
|
||||||
|
|
||||||
|
BEAST_EXPECT(
|
||||||
|
vaultAfterImpair->at(sfLossUnrealized) ==
|
||||||
|
broker.asset(PRINCIPAL_AMOUNT).value());
|
||||||
|
|
||||||
|
// Helper to get share balance for a depositor
|
||||||
|
auto const shareAsset = vaultAfterImpair->at(sfShareMPTID);
|
||||||
|
auto const getShareBalance =
|
||||||
|
[&](Account const& depositor) -> std::uint64_t {
|
||||||
|
auto const token =
|
||||||
|
env.le(keylet::mptoken(shareAsset, depositor.id()));
|
||||||
|
return token ? token->getFieldU64(sfMPTAmount) : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify both depositors have equal shares
|
||||||
|
auto const sharesLpA = getShareBalance(depositorA);
|
||||||
|
auto const sharesLpB = getShareBalance(depositorB);
|
||||||
|
BEAST_EXPECT(sharesLpA == EXPECTED_SHARES_PER_DEPOSITOR);
|
||||||
|
BEAST_EXPECT(sharesLpB == EXPECTED_SHARES_PER_DEPOSITOR);
|
||||||
|
BEAST_EXPECT(sharesLpA == sharesLpB);
|
||||||
|
|
||||||
|
// Helper to attempt withdrawal
|
||||||
|
auto const attemptWithdrawShares = [&](Account const& depositor,
|
||||||
|
std::uint64_t shareAmount,
|
||||||
|
TER expected) {
|
||||||
|
STAmount const shareAmt{MPTIssue{shareAsset}, Number(shareAmount)};
|
||||||
|
env(v.withdraw(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = broker.vaultKeylet().key,
|
||||||
|
.amount = shareAmt}),
|
||||||
|
ter(expected));
|
||||||
|
env.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Regression test: Both depositors should successfully withdraw despite
|
||||||
|
// unrealized loss. Previously failed with invariant violation:
|
||||||
|
// "withdrawal must change vault and destination balance by equal
|
||||||
|
// amount". This was caused by sharesToAssetsWithdraw rounding down,
|
||||||
|
// creating a mismatch where vaultDeltaAssets * -1 != destinationDelta
|
||||||
|
// when unrealized loss exists.
|
||||||
|
attemptWithdrawShares(depositorA, sharesLpA, tesSUCCESS);
|
||||||
|
attemptWithdrawShares(depositorB, sharesLpB, tesSUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void
|
void
|
||||||
run() override
|
run() override
|
||||||
@@ -7649,6 +7792,7 @@ public:
|
|||||||
testLoanPayLateFullPaymentBypassesPenalties();
|
testLoanPayLateFullPaymentBypassesPenalties();
|
||||||
testLoanCoverMinimumRoundingExploit();
|
testLoanCoverMinimumRoundingExploit();
|
||||||
#endif
|
#endif
|
||||||
|
testWithdrawReflectsUnrealizedLoss();
|
||||||
testInvalidLoanSet();
|
testInvalidLoanSet();
|
||||||
|
|
||||||
testCoverDepositWithdrawNonTransferableMPT();
|
testCoverDepositWithdrawNonTransferableMPT();
|
||||||
|
|||||||
@@ -32,9 +32,10 @@ public:
|
|||||||
test_limits()
|
test_limits()
|
||||||
{
|
{
|
||||||
auto const scale = Number::getMantissaScale();
|
auto const scale = Number::getMantissaScale();
|
||||||
testcase << "test_limits " << to_string(scale);
|
|
||||||
bool caught = false;
|
|
||||||
auto const minMantissa = Number::minMantissa();
|
auto const minMantissa = Number::minMantissa();
|
||||||
|
|
||||||
|
testcase << "test_limits " << to_string(scale) << ", " << minMantissa;
|
||||||
|
bool caught = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Number x =
|
Number x =
|
||||||
@@ -62,8 +63,9 @@ public:
|
|||||||
Number{},
|
Number{},
|
||||||
__LINE__);
|
__LINE__);
|
||||||
test(
|
test(
|
||||||
|
// Use 1501 to force rounding up
|
||||||
Number{false, minMantissa, 32000, Number::normalized{}} * 1'000 +
|
Number{false, minMantissa, 32000, Number::normalized{}} * 1'000 +
|
||||||
Number{false, 1'500, 32000, Number::normalized{}},
|
Number{false, 1'501, 32000, Number::normalized{}},
|
||||||
Number{false, minMantissa + 2, 32003, Number::normalized{}},
|
Number{false, minMantissa + 2, 32003, Number::normalized{}},
|
||||||
__LINE__);
|
__LINE__);
|
||||||
// 9,223,372,036,854,775,808
|
// 9,223,372,036,854,775,808
|
||||||
@@ -198,12 +200,12 @@ public:
|
|||||||
9'999'999'999'999'999'990ULL,
|
9'999'999'999'999'999'990ULL,
|
||||||
-19,
|
-19,
|
||||||
Number::normalized{}}},
|
Number::normalized{}}},
|
||||||
{Number{Number::maxRep},
|
{Number{Number::largestMantissa},
|
||||||
Number{6, -1},
|
Number{6, -1},
|
||||||
Number{Number::maxRep / 10, 1}},
|
Number{Number::largestMantissa / 10, 1}},
|
||||||
{Number{Number::maxRep - 1},
|
{Number{Number::largestMantissa - 1},
|
||||||
Number{1, 0},
|
Number{1, 0},
|
||||||
Number{Number::maxRep}},
|
Number{Number::largestMantissa}},
|
||||||
// Test extremes
|
// Test extremes
|
||||||
{
|
{
|
||||||
// Each Number operand rounds up, so the actual mantissa is
|
// Each Number operand rounds up, so the actual mantissa is
|
||||||
@@ -221,11 +223,30 @@ public:
|
|||||||
Number{2, 19},
|
Number{2, 19},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Does not round. Mantissas are going to be > maxRep, so if
|
// Does not round. Mantissas are going to be >
|
||||||
// added together as uint64_t's, the result will overflow.
|
// largestMantissa, so if added together as uint64_t's, the
|
||||||
// With addition using uint128_t, there's no problem. After
|
// result will overflow. With addition using uint128_t,
|
||||||
// normalizing, the resulting mantissa ends up less than
|
// there's no problem. After normalizing, the resulting
|
||||||
// maxRep.
|
// mantissa ends up less than largestMantissa.
|
||||||
|
Number{
|
||||||
|
false,
|
||||||
|
Number::largestMantissa,
|
||||||
|
0,
|
||||||
|
Number::normalized{}},
|
||||||
|
Number{
|
||||||
|
false,
|
||||||
|
Number::largestMantissa,
|
||||||
|
0,
|
||||||
|
Number::normalized{}},
|
||||||
|
Number{
|
||||||
|
false,
|
||||||
|
Number::largestMantissa * 2,
|
||||||
|
0,
|
||||||
|
Number::normalized{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// These mantissas round down, so adding them together won't
|
||||||
|
// have any consequences.
|
||||||
Number{
|
Number{
|
||||||
false,
|
false,
|
||||||
9'999'999'999'999'999'990ULL,
|
9'999'999'999'999'999'990ULL,
|
||||||
@@ -352,16 +373,24 @@ public:
|
|||||||
{Number{1'000'000'000'000'000'001, -18},
|
{Number{1'000'000'000'000'000'001, -18},
|
||||||
Number{1'000'000'000'000'000'000, -18},
|
Number{1'000'000'000'000'000'000, -18},
|
||||||
Number{1'000'000'000'000'000'000, -36}},
|
Number{1'000'000'000'000'000'000, -36}},
|
||||||
{Number{Number::maxRep},
|
{Number{Number::largestMantissa},
|
||||||
Number{6, -1},
|
Number{6, -1},
|
||||||
Number{Number::maxRep - 1}},
|
Number{Number::largestMantissa - 1}},
|
||||||
{Number{false, Number::maxRep + 1, 0, Number::normalized{}},
|
{Number{
|
||||||
|
false,
|
||||||
|
Number::largestMantissa + 1,
|
||||||
|
0,
|
||||||
|
Number::normalized{}},
|
||||||
Number{1, 0},
|
Number{1, 0},
|
||||||
Number{Number::maxRep / 10 + 1, 1}},
|
Number{Number::largestMantissa / 10 + 1, 1}},
|
||||||
{Number{false, Number::maxRep + 1, 0, Number::normalized{}},
|
{Number{
|
||||||
|
false,
|
||||||
|
Number::largestMantissa + 1,
|
||||||
|
0,
|
||||||
|
Number::normalized{}},
|
||||||
Number{3, 0},
|
Number{3, 0},
|
||||||
Number{Number::maxRep}},
|
Number{Number::largestMantissa}},
|
||||||
{power(2, 63), Number{3, 0}, Number{Number::maxRep}},
|
{power(2, 63), Number{3, 0}, Number{Number::largestMantissa}},
|
||||||
});
|
});
|
||||||
auto test = [this](auto const& c) {
|
auto test = [this](auto const& c) {
|
||||||
for (auto const& [x, y, z] : c)
|
for (auto const& [x, y, z] : c)
|
||||||
@@ -384,14 +413,16 @@ public:
|
|||||||
auto const scale = Number::getMantissaScale();
|
auto const scale = Number::getMantissaScale();
|
||||||
testcase << "test_mul " << to_string(scale);
|
testcase << "test_mul " << to_string(scale);
|
||||||
|
|
||||||
using Case = std::tuple<Number, Number, Number>;
|
// Case: Factor 1, Factor 2, Expected product, Line number
|
||||||
|
using Case = std::tuple<Number, Number, Number, int>;
|
||||||
auto test = [this](auto const& c) {
|
auto test = [this](auto const& c) {
|
||||||
for (auto const& [x, y, z] : c)
|
for (auto const& [x, y, z, line] : c)
|
||||||
{
|
{
|
||||||
auto const result = x * y;
|
auto const result = x * y;
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << x << " * " << y << " = " << result << ". Expected: " << z;
|
ss << x << " * " << y << " = " << result << ". Expected: " << z;
|
||||||
BEAST_EXPECTS(result == z, ss.str());
|
BEAST_EXPECTS(
|
||||||
|
result == z, ss.str() + " line: " + std::to_string(line));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
auto tests = [&](auto const& cSmall, auto const& cLarge) {
|
auto tests = [&](auto const& cSmall, auto const& cLarge) {
|
||||||
@@ -401,78 +432,105 @@ public:
|
|||||||
test(cLarge);
|
test(cLarge);
|
||||||
};
|
};
|
||||||
auto const maxMantissa = Number::maxMantissa();
|
auto const maxMantissa = Number::maxMantissa();
|
||||||
|
auto const maxInternalMantissa =
|
||||||
|
static_cast<std::uint64_t>(
|
||||||
|
static_cast<std::int64_t>(power(10, Number::mantissaLog()))) *
|
||||||
|
10 -
|
||||||
|
1;
|
||||||
|
|
||||||
saveNumberRoundMode save{Number::setround(Number::to_nearest)};
|
saveNumberRoundMode save{Number::setround(Number::to_nearest)};
|
||||||
{
|
{
|
||||||
auto const cSmall = std::to_array<Case>({
|
auto const cSmall = std::to_array<Case>({
|
||||||
{Number{7}, Number{8}, Number{56}},
|
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||||
{Number{1414213562373095, -15},
|
{Number{1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{2000000000000000, -15}},
|
Number{2000000000000000, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{-2000000000000000, -15}},
|
Number{-2000000000000000, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{-1414213562373095, -15},
|
Number{-1414213562373095, -15},
|
||||||
Number{2000000000000000, -15}},
|
Number{2000000000000000, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{3214285714285706, -15},
|
{Number{3214285714285706, -15},
|
||||||
Number{3111111111111119, -15},
|
Number{3111111111111119, -15},
|
||||||
Number{1000000000000000, -14}},
|
Number{1000000000000000, -14},
|
||||||
|
__LINE__},
|
||||||
{Number{1000000000000000, -32768},
|
{Number{1000000000000000, -32768},
|
||||||
Number{1000000000000000, -32768},
|
Number{1000000000000000, -32768},
|
||||||
Number{0}},
|
Number{0},
|
||||||
|
__LINE__},
|
||||||
// Maximum mantissa range
|
// Maximum mantissa range
|
||||||
{Number{9'999'999'999'999'999, 0},
|
{Number{9'999'999'999'999'999, 0},
|
||||||
Number{9'999'999'999'999'999, 0},
|
Number{9'999'999'999'999'999, 0},
|
||||||
Number{9'999'999'999'999'998, 16}},
|
Number{9'999'999'999'999'998, 16},
|
||||||
|
__LINE__},
|
||||||
});
|
});
|
||||||
auto const cLarge = std::to_array<Case>({
|
auto const cLarge = std::to_array<Case>({
|
||||||
// Note that items with extremely large mantissas need to be
|
// Note that items with extremely large mantissas need to be
|
||||||
// calculated, because otherwise they overflow uint64. Items
|
// calculated, because otherwise they overflow uint64. Items
|
||||||
// from C with larger mantissa
|
// from C with larger mantissa
|
||||||
{Number{7}, Number{8}, Number{56}},
|
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||||
{Number{1414213562373095, -15},
|
{Number{1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{1999999999999999862, -18}},
|
Number{1999999999999999862, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{-1999999999999999862, -18}},
|
Number{-1999999999999999862, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{-1414213562373095, -15},
|
Number{-1414213562373095, -15},
|
||||||
Number{1999999999999999862, -18}},
|
Number{1999999999999999862, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{3214285714285706, -15},
|
{Number{3214285714285706, -15},
|
||||||
Number{3111111111111119, -15},
|
Number{3111111111111119, -15},
|
||||||
Number{
|
Number{
|
||||||
false,
|
false,
|
||||||
9'999'999'999'999'999'579ULL,
|
9'999'999'999'999'999'579ULL,
|
||||||
-18,
|
-18,
|
||||||
Number::normalized{}}},
|
Number::normalized{}},
|
||||||
|
__LINE__},
|
||||||
{Number{1000000000000000000, -32768},
|
{Number{1000000000000000000, -32768},
|
||||||
Number{1000000000000000000, -32768},
|
Number{1000000000000000000, -32768},
|
||||||
Number{0}},
|
Number{0},
|
||||||
|
__LINE__},
|
||||||
// Items from cSmall expanded for the larger mantissa,
|
// Items from cSmall expanded for the larger mantissa,
|
||||||
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
|
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
|
||||||
// with higher precision
|
// with higher precision
|
||||||
{Number{1414213562373095049, -18},
|
{Number{1414213562373095049, -18},
|
||||||
Number{1414213562373095049, -18},
|
Number{1414213562373095049, -18},
|
||||||
Number{2000000000000000001, -18}},
|
Number{2000000000000000001, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095048, -18},
|
{Number{-1414213562373095048, -18},
|
||||||
Number{1414213562373095048, -18},
|
Number{1414213562373095048, -18},
|
||||||
Number{-1999999999999999998, -18}},
|
Number{-1999999999999999998, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095048, -18},
|
{Number{-1414213562373095048, -18},
|
||||||
Number{-1414213562373095049, -18},
|
Number{-1414213562373095049, -18},
|
||||||
Number{1999999999999999999, -18}},
|
Number{1999999999999999999, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{3214285714285714278, -18},
|
{Number{3214285714285714278, -18},
|
||||||
Number{3111111111111111119, -18},
|
Number{3111111111111111119, -18},
|
||||||
Number{10, 0}},
|
Number{10, 0},
|
||||||
// Maximum mantissa range - rounds up to 1e19
|
__LINE__},
|
||||||
|
// Maximum internal mantissa range - rounds up to 1e19
|
||||||
|
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
|
||||||
|
Number{false, maxInternalMantissa, 0, Number::normalized{}},
|
||||||
|
Number{1, 38},
|
||||||
|
__LINE__},
|
||||||
|
// Maximum actual mantissa range - same as int64 range
|
||||||
{Number{false, maxMantissa, 0, Number::normalized{}},
|
{Number{false, maxMantissa, 0, Number::normalized{}},
|
||||||
Number{false, maxMantissa, 0, Number::normalized{}},
|
Number{false, maxMantissa, 0, Number::normalized{}},
|
||||||
Number{1, 38}},
|
Number{85'070'591'730'234'615'85, 19},
|
||||||
|
__LINE__},
|
||||||
// Maximum int64 range
|
// Maximum int64 range
|
||||||
{Number{Number::maxRep, 0},
|
{Number{Number::largestMantissa, 0},
|
||||||
Number{Number::maxRep, 0},
|
Number{Number::largestMantissa, 0},
|
||||||
Number{85'070'591'730'234'615'85, 19}},
|
Number{85'070'591'730'234'615'85, 19},
|
||||||
|
__LINE__},
|
||||||
});
|
});
|
||||||
tests(cSmall, cLarge);
|
tests(cSmall, cLarge);
|
||||||
}
|
}
|
||||||
@@ -481,76 +539,100 @@ public:
|
|||||||
<< " towards_zero";
|
<< " towards_zero";
|
||||||
{
|
{
|
||||||
auto const cSmall = std::to_array<Case>(
|
auto const cSmall = std::to_array<Case>(
|
||||||
{{Number{7}, Number{8}, Number{56}},
|
{{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||||
{Number{1414213562373095, -15},
|
{Number{1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{1999999999999999, -15}},
|
Number{1999999999999999, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{-1999999999999999, -15}},
|
Number{-1999999999999999, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{-1414213562373095, -15},
|
Number{-1414213562373095, -15},
|
||||||
Number{1999999999999999, -15}},
|
Number{1999999999999999, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{3214285714285706, -15},
|
{Number{3214285714285706, -15},
|
||||||
Number{3111111111111119, -15},
|
Number{3111111111111119, -15},
|
||||||
Number{9999999999999999, -15}},
|
Number{9999999999999999, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{1000000000000000, -32768},
|
{Number{1000000000000000, -32768},
|
||||||
Number{1000000000000000, -32768},
|
Number{1000000000000000, -32768},
|
||||||
Number{0}}});
|
Number{0},
|
||||||
|
__LINE__}});
|
||||||
auto const cLarge = std::to_array<Case>(
|
auto const cLarge = std::to_array<Case>(
|
||||||
// Note that items with extremely large mantissas need to be
|
// Note that items with extremely large mantissas need to be
|
||||||
// calculated, because otherwise they overflow uint64. Items
|
// calculated, because otherwise they overflow uint64. Items
|
||||||
// from C with larger mantissa
|
// from C with larger mantissa
|
||||||
{
|
{
|
||||||
{Number{7}, Number{8}, Number{56}},
|
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||||
{Number{1414213562373095, -15},
|
{Number{1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{1999999999999999861, -18}},
|
Number{1999999999999999861, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{-1999999999999999861, -18}},
|
Number{-1999999999999999861, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{-1414213562373095, -15},
|
Number{-1414213562373095, -15},
|
||||||
Number{1999999999999999861, -18}},
|
Number{1999999999999999861, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{3214285714285706, -15},
|
{Number{3214285714285706, -15},
|
||||||
Number{3111111111111119, -15},
|
Number{3111111111111119, -15},
|
||||||
Number{
|
Number{
|
||||||
false,
|
false,
|
||||||
9999999999999999579ULL,
|
9999999999999999579ULL,
|
||||||
-18,
|
-18,
|
||||||
Number::normalized{}}},
|
Number::normalized{}},
|
||||||
|
__LINE__},
|
||||||
{Number{1000000000000000000, -32768},
|
{Number{1000000000000000000, -32768},
|
||||||
Number{1000000000000000000, -32768},
|
Number{1000000000000000000, -32768},
|
||||||
Number{0}},
|
Number{0},
|
||||||
|
__LINE__},
|
||||||
// Items from cSmall expanded for the larger mantissa,
|
// Items from cSmall expanded for the larger mantissa,
|
||||||
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
|
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
|
||||||
// with higher precision
|
// with higher precision
|
||||||
{Number{1414213562373095049, -18},
|
{Number{1414213562373095049, -18},
|
||||||
Number{1414213562373095049, -18},
|
Number{1414213562373095049, -18},
|
||||||
Number{2, 0}},
|
Number{2, 0},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095048, -18},
|
{Number{-1414213562373095048, -18},
|
||||||
Number{1414213562373095048, -18},
|
Number{1414213562373095048, -18},
|
||||||
Number{-1999999999999999997, -18}},
|
Number{-1999999999999999997, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095048, -18},
|
{Number{-1414213562373095048, -18},
|
||||||
Number{-1414213562373095049, -18},
|
Number{-1414213562373095049, -18},
|
||||||
Number{1999999999999999999, -18}},
|
Number{1999999999999999999, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{3214285714285714278, -18},
|
{Number{3214285714285714278, -18},
|
||||||
Number{3111111111111111119, -18},
|
Number{3111111111111111119, -18},
|
||||||
Number{10, 0}},
|
Number{10, 0},
|
||||||
// Maximum mantissa range - rounds down to maxMantissa/10e1
|
__LINE__},
|
||||||
|
// Maximum internal mantissa range - rounds down to
|
||||||
|
// maxMantissa/10e1
|
||||||
// 99'999'999'999'999'999'800'000'000'000'000'000'100
|
// 99'999'999'999'999'999'800'000'000'000'000'000'100
|
||||||
{Number{false, maxMantissa, 0, Number::normalized{}},
|
{Number{
|
||||||
Number{false, maxMantissa, 0, Number::normalized{}},
|
false, maxInternalMantissa, 0, Number::normalized{}},
|
||||||
|
Number{
|
||||||
|
false, maxInternalMantissa, 0, Number::normalized{}},
|
||||||
Number{
|
Number{
|
||||||
false,
|
false,
|
||||||
maxMantissa / 10 - 1,
|
maxInternalMantissa / 10 - 1,
|
||||||
20,
|
20,
|
||||||
Number::normalized{}}},
|
Number::normalized{}},
|
||||||
|
__LINE__},
|
||||||
|
// Maximum actual mantissa range - same as int64
|
||||||
|
{Number{false, maxMantissa, 0, Number::normalized{}},
|
||||||
|
Number{false, maxMantissa, 0, Number::normalized{}},
|
||||||
|
Number{85'070'591'730'234'615'84, 19},
|
||||||
|
__LINE__},
|
||||||
// Maximum int64 range
|
// Maximum int64 range
|
||||||
// 85'070'591'730'234'615'847'396'907'784'232'501'249
|
// 85'070'591'730'234'615'847'396'907'784'232'501'249
|
||||||
{Number{Number::maxRep, 0},
|
{Number{Number::largestMantissa, 0},
|
||||||
Number{Number::maxRep, 0},
|
Number{Number::largestMantissa, 0},
|
||||||
Number{85'070'591'730'234'615'84, 19}},
|
Number{85'070'591'730'234'615'84, 19},
|
||||||
|
__LINE__},
|
||||||
});
|
});
|
||||||
tests(cSmall, cLarge);
|
tests(cSmall, cLarge);
|
||||||
}
|
}
|
||||||
@@ -559,76 +641,100 @@ public:
|
|||||||
<< " downward";
|
<< " downward";
|
||||||
{
|
{
|
||||||
auto const cSmall = std::to_array<Case>(
|
auto const cSmall = std::to_array<Case>(
|
||||||
{{Number{7}, Number{8}, Number{56}},
|
{{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||||
{Number{1414213562373095, -15},
|
{Number{1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{1999999999999999, -15}},
|
Number{1999999999999999, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{-2000000000000000, -15}},
|
Number{-2000000000000000, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{-1414213562373095, -15},
|
Number{-1414213562373095, -15},
|
||||||
Number{1999999999999999, -15}},
|
Number{1999999999999999, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{3214285714285706, -15},
|
{Number{3214285714285706, -15},
|
||||||
Number{3111111111111119, -15},
|
Number{3111111111111119, -15},
|
||||||
Number{9999999999999999, -15}},
|
Number{9999999999999999, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{1000000000000000, -32768},
|
{Number{1000000000000000, -32768},
|
||||||
Number{1000000000000000, -32768},
|
Number{1000000000000000, -32768},
|
||||||
Number{0}}});
|
Number{0},
|
||||||
|
__LINE__}});
|
||||||
auto const cLarge = std::to_array<Case>(
|
auto const cLarge = std::to_array<Case>(
|
||||||
// Note that items with extremely large mantissas need to be
|
// Note that items with extremely large mantissas need to be
|
||||||
// calculated, because otherwise they overflow uint64. Items
|
// calculated, because otherwise they overflow uint64. Items
|
||||||
// from C with larger mantissa
|
// from C with larger mantissa
|
||||||
{
|
{
|
||||||
{Number{7}, Number{8}, Number{56}},
|
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||||
{Number{1414213562373095, -15},
|
{Number{1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{1999999999999999861, -18}},
|
Number{1999999999999999861, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{-1999999999999999862, -18}},
|
Number{-1999999999999999862, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{-1414213562373095, -15},
|
Number{-1414213562373095, -15},
|
||||||
Number{1999999999999999861, -18}},
|
Number{1999999999999999861, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{3214285714285706, -15},
|
{Number{3214285714285706, -15},
|
||||||
Number{3111111111111119, -15},
|
Number{3111111111111119, -15},
|
||||||
Number{
|
Number{
|
||||||
false,
|
false,
|
||||||
9'999'999'999'999'999'579ULL,
|
9'999'999'999'999'999'579ULL,
|
||||||
-18,
|
-18,
|
||||||
Number::normalized{}}},
|
Number::normalized{}},
|
||||||
|
__LINE__},
|
||||||
{Number{1000000000000000000, -32768},
|
{Number{1000000000000000000, -32768},
|
||||||
Number{1000000000000000000, -32768},
|
Number{1000000000000000000, -32768},
|
||||||
Number{0}},
|
Number{0},
|
||||||
|
__LINE__},
|
||||||
// Items from cSmall expanded for the larger mantissa,
|
// Items from cSmall expanded for the larger mantissa,
|
||||||
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
|
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
|
||||||
// with higher precision
|
// with higher precision
|
||||||
{Number{1414213562373095049, -18},
|
{Number{1414213562373095049, -18},
|
||||||
Number{1414213562373095049, -18},
|
Number{1414213562373095049, -18},
|
||||||
Number{2, 0}},
|
Number{2, 0},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095048, -18},
|
{Number{-1414213562373095048, -18},
|
||||||
Number{1414213562373095048, -18},
|
Number{1414213562373095048, -18},
|
||||||
Number{-1999999999999999998, -18}},
|
Number{-1999999999999999998, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095048, -18},
|
{Number{-1414213562373095048, -18},
|
||||||
Number{-1414213562373095049, -18},
|
Number{-1414213562373095049, -18},
|
||||||
Number{1999999999999999999, -18}},
|
Number{1999999999999999999, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{3214285714285714278, -18},
|
{Number{3214285714285714278, -18},
|
||||||
Number{3111111111111111119, -18},
|
Number{3111111111111111119, -18},
|
||||||
Number{10, 0}},
|
Number{10, 0},
|
||||||
// Maximum mantissa range - rounds down to maxMantissa/10e1
|
__LINE__},
|
||||||
|
// Maximum internal mantissa range - rounds down to
|
||||||
|
// maxMantissa/10-1
|
||||||
// 99'999'999'999'999'999'800'000'000'000'000'000'100
|
// 99'999'999'999'999'999'800'000'000'000'000'000'100
|
||||||
{Number{false, maxMantissa, 0, Number::normalized{}},
|
{Number{
|
||||||
Number{false, maxMantissa, 0, Number::normalized{}},
|
false, maxInternalMantissa, 0, Number::normalized{}},
|
||||||
|
Number{
|
||||||
|
false, maxInternalMantissa, 0, Number::normalized{}},
|
||||||
Number{
|
Number{
|
||||||
false,
|
false,
|
||||||
maxMantissa / 10 - 1,
|
maxInternalMantissa / 10 - 1,
|
||||||
20,
|
20,
|
||||||
Number::normalized{}}},
|
Number::normalized{}},
|
||||||
|
__LINE__},
|
||||||
|
// Maximum mantissa range - same as int64
|
||||||
|
{Number{false, maxMantissa, 0, Number::normalized{}},
|
||||||
|
Number{false, maxMantissa, 0, Number::normalized{}},
|
||||||
|
Number{85'070'591'730'234'615'84, 19},
|
||||||
|
__LINE__},
|
||||||
// Maximum int64 range
|
// Maximum int64 range
|
||||||
// 85'070'591'730'234'615'847'396'907'784'232'501'249
|
// 85'070'591'730'234'615'847'396'907'784'232'501'249
|
||||||
{Number{Number::maxRep, 0},
|
{Number{Number::largestMantissa, 0},
|
||||||
Number{Number::maxRep, 0},
|
Number{Number::largestMantissa, 0},
|
||||||
Number{85'070'591'730'234'615'84, 19}},
|
Number{85'070'591'730'234'615'84, 19},
|
||||||
|
__LINE__},
|
||||||
});
|
});
|
||||||
tests(cSmall, cLarge);
|
tests(cSmall, cLarge);
|
||||||
}
|
}
|
||||||
@@ -637,68 +743,91 @@ public:
|
|||||||
<< " upward";
|
<< " upward";
|
||||||
{
|
{
|
||||||
auto const cSmall = std::to_array<Case>(
|
auto const cSmall = std::to_array<Case>(
|
||||||
{{Number{7}, Number{8}, Number{56}},
|
{{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||||
{Number{1414213562373095, -15},
|
{Number{1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{2000000000000000, -15}},
|
Number{2000000000000000, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{-1999999999999999, -15}},
|
Number{-1999999999999999, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{-1414213562373095, -15},
|
Number{-1414213562373095, -15},
|
||||||
Number{2000000000000000, -15}},
|
Number{2000000000000000, -15},
|
||||||
|
__LINE__},
|
||||||
{Number{3214285714285706, -15},
|
{Number{3214285714285706, -15},
|
||||||
Number{3111111111111119, -15},
|
Number{3111111111111119, -15},
|
||||||
Number{1000000000000000, -14}},
|
Number{1000000000000000, -14},
|
||||||
|
__LINE__},
|
||||||
{Number{1000000000000000, -32768},
|
{Number{1000000000000000, -32768},
|
||||||
Number{1000000000000000, -32768},
|
Number{1000000000000000, -32768},
|
||||||
Number{0}}});
|
Number{0},
|
||||||
|
__LINE__}});
|
||||||
auto const cLarge = std::to_array<Case>(
|
auto const cLarge = std::to_array<Case>(
|
||||||
// Note that items with extremely large mantissas need to be
|
// Note that items with extremely large mantissas need to be
|
||||||
// calculated, because otherwise they overflow uint64. Items
|
// calculated, because otherwise they overflow uint64. Items
|
||||||
// from C with larger mantissa
|
// from C with larger mantissa
|
||||||
{
|
{
|
||||||
{Number{7}, Number{8}, Number{56}},
|
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||||
{Number{1414213562373095, -15},
|
{Number{1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{1999999999999999862, -18}},
|
Number{1999999999999999862, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{1414213562373095, -15},
|
Number{1414213562373095, -15},
|
||||||
Number{-1999999999999999861, -18}},
|
Number{-1999999999999999861, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095, -15},
|
{Number{-1414213562373095, -15},
|
||||||
Number{-1414213562373095, -15},
|
Number{-1414213562373095, -15},
|
||||||
Number{1999999999999999862, -18}},
|
Number{1999999999999999862, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{3214285714285706, -15},
|
{Number{3214285714285706, -15},
|
||||||
Number{3111111111111119, -15},
|
Number{3111111111111119, -15},
|
||||||
Number{999999999999999958, -17}},
|
Number{999999999999999958, -17},
|
||||||
|
__LINE__},
|
||||||
{Number{1000000000000000000, -32768},
|
{Number{1000000000000000000, -32768},
|
||||||
Number{1000000000000000000, -32768},
|
Number{1000000000000000000, -32768},
|
||||||
Number{0}},
|
Number{0},
|
||||||
|
__LINE__},
|
||||||
// Items from cSmall expanded for the larger mantissa,
|
// Items from cSmall expanded for the larger mantissa,
|
||||||
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
|
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
|
||||||
// with higher precision
|
// with higher precision
|
||||||
{Number{1414213562373095049, -18},
|
{Number{1414213562373095049, -18},
|
||||||
Number{1414213562373095049, -18},
|
Number{1414213562373095049, -18},
|
||||||
Number{2000000000000000001, -18}},
|
Number{2000000000000000001, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095048, -18},
|
{Number{-1414213562373095048, -18},
|
||||||
Number{1414213562373095048, -18},
|
Number{1414213562373095048, -18},
|
||||||
Number{-1999999999999999997, -18}},
|
Number{-1999999999999999997, -18},
|
||||||
|
__LINE__},
|
||||||
{Number{-1414213562373095048, -18},
|
{Number{-1414213562373095048, -18},
|
||||||
Number{-1414213562373095049, -18},
|
Number{-1414213562373095049, -18},
|
||||||
Number{2, 0}},
|
Number{2, 0},
|
||||||
|
__LINE__},
|
||||||
{Number{3214285714285714278, -18},
|
{Number{3214285714285714278, -18},
|
||||||
Number{3111111111111111119, -18},
|
Number{3111111111111111119, -18},
|
||||||
Number{1000000000000000001, -17}},
|
Number{1000000000000000001, -17},
|
||||||
// Maximum mantissa range - rounds up to minMantissa*10
|
__LINE__},
|
||||||
// 1e19*1e19=1e38
|
// Maximum internal mantissa range - rounds up to
|
||||||
|
// minMantissa*10 1e19*1e19=1e38
|
||||||
|
{Number{
|
||||||
|
false, maxInternalMantissa, 0, Number::normalized{}},
|
||||||
|
Number{
|
||||||
|
false, maxInternalMantissa, 0, Number::normalized{}},
|
||||||
|
Number{1, 38},
|
||||||
|
__LINE__},
|
||||||
|
// Maximum mantissa range - same as int64
|
||||||
{Number{false, maxMantissa, 0, Number::normalized{}},
|
{Number{false, maxMantissa, 0, Number::normalized{}},
|
||||||
Number{false, maxMantissa, 0, Number::normalized{}},
|
Number{false, maxMantissa, 0, Number::normalized{}},
|
||||||
Number{1, 38}},
|
Number{85'070'591'730'234'615'85, 19},
|
||||||
|
__LINE__},
|
||||||
// Maximum int64 range
|
// Maximum int64 range
|
||||||
// 85'070'591'730'234'615'847'396'907'784'232'501'249
|
// 85'070'591'730'234'615'847'396'907'784'232'501'249
|
||||||
{Number{Number::maxRep, 0},
|
{Number{Number::largestMantissa, 0},
|
||||||
Number{Number::maxRep, 0},
|
Number{Number::largestMantissa, 0},
|
||||||
Number{85'070'591'730'234'615'85, 19}},
|
Number{85'070'591'730'234'615'85, 19},
|
||||||
|
__LINE__},
|
||||||
});
|
});
|
||||||
tests(cSmall, cLarge);
|
tests(cSmall, cLarge);
|
||||||
}
|
}
|
||||||
@@ -971,6 +1100,12 @@ public:
|
|||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
auto const maxInternalMantissa =
|
||||||
|
static_cast<std::uint64_t>(
|
||||||
|
static_cast<std::int64_t>(power(10, Number::mantissaLog()))) *
|
||||||
|
10 -
|
||||||
|
1;
|
||||||
|
|
||||||
auto const cSmall = std::to_array<Case>(
|
auto const cSmall = std::to_array<Case>(
|
||||||
{{Number{2}, 2, Number{1414213562373095049, -18}},
|
{{Number{2}, 2, Number{1414213562373095049, -18}},
|
||||||
{Number{2'000'000}, 2, Number{1414213562373095049, -15}},
|
{Number{2'000'000}, 2, Number{1414213562373095049, -15}},
|
||||||
@@ -982,17 +1117,17 @@ public:
|
|||||||
{Number{0}, 5, Number{0}},
|
{Number{0}, 5, Number{0}},
|
||||||
{Number{5625, -4}, 2, Number{75, -2}}});
|
{Number{5625, -4}, 2, Number{75, -2}}});
|
||||||
auto const cLarge = std::to_array<Case>({
|
auto const cLarge = std::to_array<Case>({
|
||||||
{Number{false, Number::maxMantissa() - 9, -1, Number::normalized{}},
|
{Number{false, maxInternalMantissa - 9, -1, Number::normalized{}},
|
||||||
2,
|
2,
|
||||||
Number{false, 999'999'999'999'999'999, -9, Number::normalized{}}},
|
Number{false, 999'999'999'999'999'999, -9, Number::normalized{}}},
|
||||||
{Number{false, Number::maxMantissa() - 9, 0, Number::normalized{}},
|
{Number{false, maxInternalMantissa - 9, 0, Number::normalized{}},
|
||||||
2,
|
2,
|
||||||
Number{
|
Number{
|
||||||
false, 3'162'277'660'168'379'330, -9, Number::normalized{}}},
|
false, 3'162'277'660'168'379'330, -9, Number::normalized{}}},
|
||||||
{Number{Number::maxRep},
|
{Number{Number::largestMantissa},
|
||||||
2,
|
2,
|
||||||
Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}},
|
Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}},
|
||||||
{Number{Number::maxRep},
|
{Number{Number::largestMantissa},
|
||||||
4,
|
4,
|
||||||
Number{false, 55'108'98747006743627, -14, Number::normalized{}}},
|
Number{false, 55'108'98747006743627, -14, Number::normalized{}}},
|
||||||
});
|
});
|
||||||
@@ -1042,6 +1177,9 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto const maxInternalMantissa =
|
||||||
|
power(10, Number::mantissaLog()) * 10 - 1;
|
||||||
|
|
||||||
auto const cSmall = std::to_array<Number>({
|
auto const cSmall = std::to_array<Number>({
|
||||||
Number{2},
|
Number{2},
|
||||||
Number{2'000'000},
|
Number{2'000'000},
|
||||||
@@ -1051,7 +1189,10 @@ public:
|
|||||||
Number{5, -1},
|
Number{5, -1},
|
||||||
Number{0},
|
Number{0},
|
||||||
Number{5625, -4},
|
Number{5625, -4},
|
||||||
Number{Number::maxRep},
|
Number{Number::largestMantissa},
|
||||||
|
maxInternalMantissa,
|
||||||
|
Number{Number::minMantissa(), 0, Number::unchecked{}},
|
||||||
|
Number{Number::maxMantissa(), 0, Number::unchecked{}},
|
||||||
});
|
});
|
||||||
test(cSmall);
|
test(cSmall);
|
||||||
bool caught = false;
|
bool caught = false;
|
||||||
@@ -1417,20 +1558,20 @@ public:
|
|||||||
case MantissaRange::large:
|
case MantissaRange::large:
|
||||||
// Test the edges
|
// Test the edges
|
||||||
// ((exponent < -(28)) || (exponent > -(8)))))
|
// ((exponent < -(28)) || (exponent > -(8)))))
|
||||||
test(Number::min(), "1e-32750");
|
test(Number::min(), "922337203685477581e-32768");
|
||||||
test(Number::max(), "9223372036854775807e32768");
|
test(Number::max(), "9223372036854775807e32768");
|
||||||
test(Number::lowest(), "-9223372036854775807e32768");
|
test(Number::lowest(), "-9223372036854775807e32768");
|
||||||
{
|
{
|
||||||
NumberRoundModeGuard mg(Number::towards_zero);
|
NumberRoundModeGuard mg(Number::towards_zero);
|
||||||
|
|
||||||
auto const maxMantissa = Number::maxMantissa();
|
auto const maxMantissa = Number::maxMantissa();
|
||||||
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL);
|
BEAST_EXPECT(maxMantissa == 9'223'372'036'854'775'807ULL);
|
||||||
test(
|
test(
|
||||||
Number{false, maxMantissa, 0, Number::normalized{}},
|
Number{false, maxMantissa, 0, Number::normalized{}},
|
||||||
"9999999999999999990");
|
"9223372036854775807");
|
||||||
test(
|
test(
|
||||||
Number{true, maxMantissa, 0, Number::normalized{}},
|
Number{true, maxMantissa, 0, Number::normalized{}},
|
||||||
"-9999999999999999990");
|
"-9223372036854775807");
|
||||||
|
|
||||||
test(
|
test(
|
||||||
Number{std::numeric_limits<std::int64_t>::max(), 0},
|
Number{std::numeric_limits<std::int64_t>::max(), 0},
|
||||||
@@ -1671,7 +1812,7 @@ public:
|
|||||||
Number const initalXrp{INITIAL_XRP};
|
Number const initalXrp{INITIAL_XRP};
|
||||||
BEAST_EXPECT(initalXrp.exponent() > 0);
|
BEAST_EXPECT(initalXrp.exponent() > 0);
|
||||||
|
|
||||||
Number const maxInt64{Number::maxRep};
|
Number const maxInt64{Number::largestMantissa};
|
||||||
BEAST_EXPECT(maxInt64.exponent() > 0);
|
BEAST_EXPECT(maxInt64.exponent() > 0);
|
||||||
// 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits
|
// 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
@@ -1691,7 +1832,7 @@ public:
|
|||||||
Number const initalXrp{INITIAL_XRP};
|
Number const initalXrp{INITIAL_XRP};
|
||||||
BEAST_EXPECT(initalXrp.exponent() <= 0);
|
BEAST_EXPECT(initalXrp.exponent() <= 0);
|
||||||
|
|
||||||
Number const maxInt64{Number::maxRep};
|
Number const maxInt64{Number::largestMantissa};
|
||||||
BEAST_EXPECT(maxInt64.exponent() <= 0);
|
BEAST_EXPECT(maxInt64.exponent() <= 0);
|
||||||
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits
|
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
@@ -1699,16 +1840,247 @@ public:
|
|||||||
|
|
||||||
NumberRoundModeGuard mg(Number::towards_zero);
|
NumberRoundModeGuard mg(Number::towards_zero);
|
||||||
|
|
||||||
auto const maxMantissa = Number::maxMantissa();
|
{
|
||||||
Number const max =
|
auto const maxInternalMantissa =
|
||||||
Number{false, maxMantissa, 0, Number::normalized{}};
|
static_cast<std::uint64_t>(static_cast<std::int64_t>(
|
||||||
BEAST_EXPECT(max.mantissa() == maxMantissa / 10);
|
power(10, Number::mantissaLog()))) *
|
||||||
BEAST_EXPECT(max.exponent() == 1);
|
10 -
|
||||||
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
|
1;
|
||||||
// digits
|
|
||||||
BEAST_EXPECT((
|
// Rounds down to fit under 2^63
|
||||||
power(max, 2) ==
|
Number const max =
|
||||||
Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}}));
|
Number{false, maxInternalMantissa, 0, Number::normalized{}};
|
||||||
|
// No alterations by the accessors
|
||||||
|
BEAST_EXPECT(max.mantissa() == maxInternalMantissa / 10);
|
||||||
|
BEAST_EXPECT(max.exponent() == 1);
|
||||||
|
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
|
||||||
|
// digits
|
||||||
|
BEAST_EXPECT(
|
||||||
|
(power(max, 2) ==
|
||||||
|
Number{
|
||||||
|
false,
|
||||||
|
maxInternalMantissa / 10 - 1,
|
||||||
|
20,
|
||||||
|
Number::normalized{}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto const maxMantissa = Number::maxMantissa();
|
||||||
|
Number const max =
|
||||||
|
Number{false, maxMantissa, 0, Number::normalized{}};
|
||||||
|
// No alterations by the accessors
|
||||||
|
BEAST_EXPECT(max.mantissa() == maxMantissa);
|
||||||
|
BEAST_EXPECT(max.exponent() == 0);
|
||||||
|
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - also 38
|
||||||
|
// digits
|
||||||
|
BEAST_EXPECT(
|
||||||
|
(power(max, 2) ==
|
||||||
|
Number{
|
||||||
|
false,
|
||||||
|
85'070'591'730'234'615'84,
|
||||||
|
19,
|
||||||
|
Number::normalized{}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testNormalizeToRange()
|
||||||
|
{
|
||||||
|
// Test edge-cases of normalizeToRange
|
||||||
|
auto const scale = Number::getMantissaScale();
|
||||||
|
testcase << "normalizeToRange " << to_string(scale);
|
||||||
|
|
||||||
|
auto test = [this](
|
||||||
|
Number const& n,
|
||||||
|
auto const rangeMin,
|
||||||
|
auto const rangeMax,
|
||||||
|
auto const expectedMantissa,
|
||||||
|
auto const expectedExponent,
|
||||||
|
auto const line) {
|
||||||
|
auto const normalized = n.normalizeToRange(rangeMin, rangeMax);
|
||||||
|
BEAST_EXPECTS(
|
||||||
|
normalized.first == expectedMantissa,
|
||||||
|
"Number " + to_string(n) + " scaled to " +
|
||||||
|
std::to_string(rangeMax) +
|
||||||
|
". Expected mantissa:" + std::to_string(expectedMantissa) +
|
||||||
|
", got: " + std::to_string(normalized.first) + " @ " +
|
||||||
|
std::to_string(line));
|
||||||
|
BEAST_EXPECTS(
|
||||||
|
normalized.second == expectedExponent,
|
||||||
|
"Number " + to_string(n) + " scaled to " +
|
||||||
|
std::to_string(rangeMax) +
|
||||||
|
". Expected exponent:" + std::to_string(expectedExponent) +
|
||||||
|
", got: " + std::to_string(normalized.second) + " @ " +
|
||||||
|
std::to_string(line));
|
||||||
|
};
|
||||||
|
|
||||||
|
std::int64_t constexpr iRangeMin = 100;
|
||||||
|
std::int64_t constexpr iRangeMax = 999;
|
||||||
|
|
||||||
|
std::uint64_t constexpr uRangeMin = 100;
|
||||||
|
std::uint64_t constexpr uRangeMax = 999;
|
||||||
|
|
||||||
|
constexpr static MantissaRange largeRange{MantissaRange::large};
|
||||||
|
|
||||||
|
std::int64_t constexpr iBigMin = largeRange.min;
|
||||||
|
std::int64_t constexpr iBigMax = largeRange.max;
|
||||||
|
|
||||||
|
auto const testSuite = [&](Number const& n,
|
||||||
|
auto const expectedSmallMantissa,
|
||||||
|
auto const expectedSmallExponent,
|
||||||
|
auto const expectedLargeMantissa,
|
||||||
|
auto const expectedLargeExponent,
|
||||||
|
auto const line) {
|
||||||
|
test(
|
||||||
|
n,
|
||||||
|
iRangeMin,
|
||||||
|
iRangeMax,
|
||||||
|
expectedSmallMantissa,
|
||||||
|
expectedSmallExponent,
|
||||||
|
line);
|
||||||
|
test(
|
||||||
|
n,
|
||||||
|
iBigMin,
|
||||||
|
iBigMax,
|
||||||
|
expectedLargeMantissa,
|
||||||
|
expectedLargeExponent,
|
||||||
|
line);
|
||||||
|
|
||||||
|
// Only test non-negative. testing a negative number with an
|
||||||
|
// unsigned range will assert, and asserts can't be tested.
|
||||||
|
if (n.signum() >= 0)
|
||||||
|
{
|
||||||
|
test(
|
||||||
|
n,
|
||||||
|
uRangeMin,
|
||||||
|
uRangeMax,
|
||||||
|
expectedSmallMantissa,
|
||||||
|
expectedSmallExponent,
|
||||||
|
line);
|
||||||
|
test(
|
||||||
|
n,
|
||||||
|
largeRange.min,
|
||||||
|
largeRange.max,
|
||||||
|
expectedLargeMantissa,
|
||||||
|
expectedLargeExponent,
|
||||||
|
line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
// zero
|
||||||
|
Number const n{0};
|
||||||
|
|
||||||
|
testSuite(
|
||||||
|
n,
|
||||||
|
0,
|
||||||
|
std::numeric_limits<int>::lowest(),
|
||||||
|
0,
|
||||||
|
std::numeric_limits<int>::lowest(),
|
||||||
|
__LINE__);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Small positive number
|
||||||
|
Number const n{2};
|
||||||
|
|
||||||
|
testSuite(n, 200, -2, 2'000'000'000'000'000'000, -18, __LINE__);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Negative number
|
||||||
|
Number const n{-2};
|
||||||
|
|
||||||
|
testSuite(n, -200, -2, -2'000'000'000'000'000'000, -18, __LINE__);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Biggest valid mantissa
|
||||||
|
Number const n{Number::largestMantissa, 0, Number::normalized{}};
|
||||||
|
|
||||||
|
if (scale == MantissaRange::small)
|
||||||
|
// With the small mantissa range, the value rounds up. Because
|
||||||
|
// it rounds up, when scaling up to the full int64 range, it
|
||||||
|
// can't go over the max, so it is one digit smaller than the
|
||||||
|
// full value.
|
||||||
|
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
|
||||||
|
else
|
||||||
|
testSuite(n, 922, 16, Number::largestMantissa, 0, __LINE__);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Biggest valid mantissa + 1
|
||||||
|
Number const n{
|
||||||
|
Number::largestMantissa + 1, 0, Number::normalized{}};
|
||||||
|
|
||||||
|
if (scale == MantissaRange::small)
|
||||||
|
// With the small mantissa range, the value rounds up. Because
|
||||||
|
// it rounds up, when scaling up to the full int64 range, it
|
||||||
|
// can't go over the max, so it is one digit smaller than the
|
||||||
|
// full value.
|
||||||
|
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
|
||||||
|
else
|
||||||
|
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Biggest valid mantissa + 2
|
||||||
|
Number const n{
|
||||||
|
Number::largestMantissa + 2, 0, Number::normalized{}};
|
||||||
|
|
||||||
|
if (scale == MantissaRange::small)
|
||||||
|
// With the small mantissa range, the value rounds up. Because
|
||||||
|
// it rounds up, when scaling up to the full int64 range, it
|
||||||
|
// can't go over the max, so it is one digit smaller than the
|
||||||
|
// full value.
|
||||||
|
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
|
||||||
|
else
|
||||||
|
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Biggest valid mantissa + 3
|
||||||
|
Number const n{
|
||||||
|
Number::largestMantissa + 3, 0, Number::normalized{}};
|
||||||
|
|
||||||
|
if (scale == MantissaRange::small)
|
||||||
|
// With the small mantissa range, the value rounds up. Because
|
||||||
|
// it rounds up, when scaling up to the full int64 range, it
|
||||||
|
// can't go over the max, so it is one digit smaller than the
|
||||||
|
// full value.
|
||||||
|
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
|
||||||
|
else
|
||||||
|
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// int64 min
|
||||||
|
Number const n{std::numeric_limits<std::int64_t>::min(), 0};
|
||||||
|
|
||||||
|
if (scale == MantissaRange::small)
|
||||||
|
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
|
||||||
|
else
|
||||||
|
testSuite(n, -922, 16, -922'337'203'685'477'581, 1, __LINE__);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// int64 min + 1
|
||||||
|
Number const n{std::numeric_limits<std::int64_t>::min() + 1, 0};
|
||||||
|
|
||||||
|
if (scale == MantissaRange::small)
|
||||||
|
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
|
||||||
|
else
|
||||||
|
testSuite(n, -922, 16, -9'223'372'036'854'775'807, 0, __LINE__);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// int64 min - 1
|
||||||
|
// Need to cast to uint, even though we're dealing with a negative
|
||||||
|
// number to avoid overflow and UB
|
||||||
|
Number const n{
|
||||||
|
true,
|
||||||
|
-static_cast<std::uint64_t>(
|
||||||
|
std::numeric_limits<std::int64_t>::min()) +
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
Number::normalized{}};
|
||||||
|
|
||||||
|
if (scale == MantissaRange::small)
|
||||||
|
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
|
||||||
|
else
|
||||||
|
testSuite(n, -922, 16, -922'337'203'685'477'581, 1, __LINE__);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1739,6 +2111,7 @@ public:
|
|||||||
test_truncate();
|
test_truncate();
|
||||||
testRounding();
|
testRounding();
|
||||||
testInt64();
|
testInt64();
|
||||||
|
testNormalizeToRange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -176,8 +176,7 @@ getAssetsTotalScale(SLE::const_ref vaultSle)
|
|||||||
{
|
{
|
||||||
if (!vaultSle)
|
if (!vaultSle)
|
||||||
return Number::minExponent - 1; // LCOV_EXCL_LINE
|
return Number::minExponent - 1; // LCOV_EXCL_LINE
|
||||||
return STAmount{vaultSle->at(sfAsset), vaultSle->at(sfAssetsTotal)}
|
return vaultSle->at(sfAssetsTotal).scale<STAmount>(vaultSle->at(sfAsset));
|
||||||
.exponent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TER
|
TER
|
||||||
|
|||||||
@@ -19,10 +19,11 @@
|
|||||||
#include <xrpl/protocol/SystemParameters.h>
|
#include <xrpl/protocol/SystemParameters.h>
|
||||||
#include <xrpl/protocol/TER.h>
|
#include <xrpl/protocol/TER.h>
|
||||||
#include <xrpl/protocol/TxFormats.h>
|
#include <xrpl/protocol/TxFormats.h>
|
||||||
#include <xrpl/protocol/Units.h>
|
|
||||||
#include <xrpl/protocol/nftPageMask.h>
|
#include <xrpl/protocol/nftPageMask.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <initializer_list>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace xrpl {
|
namespace xrpl {
|
||||||
@@ -2663,8 +2664,13 @@ ValidVault::visitEntry(
|
|||||||
// Number balanceDelta will capture the difference (delta) between "before"
|
// Number balanceDelta will capture the difference (delta) between "before"
|
||||||
// state (zero if created) and "after" state (zero if destroyed), so the
|
// state (zero if created) and "after" state (zero if destroyed), so the
|
||||||
// invariants can validate that the change in account balances matches the
|
// invariants can validate that the change in account balances matches the
|
||||||
// change in vault balances, stored to deltas_ at the end of this function.
|
// balanceDelta captures the difference (delta) between "before"
|
||||||
Number balanceDelta{};
|
// state (zero if created) and "after" state (zero if destroyed), and
|
||||||
|
// preserves value scale (exponent) to round values to the same scale during
|
||||||
|
// validation. It is used to validate that the change in account
|
||||||
|
// balances matches the change in vault balances, stored to deltas_ at the
|
||||||
|
// end of this function.
|
||||||
|
DeltaInfo balanceDelta{numZero, std::nullopt};
|
||||||
|
|
||||||
std::int8_t sign = 0;
|
std::int8_t sign = 0;
|
||||||
if (before)
|
if (before)
|
||||||
@@ -2678,20 +2684,35 @@ ValidVault::visitEntry(
|
|||||||
// At this moment we have no way of telling if this object holds
|
// At this moment we have no way of telling if this object holds
|
||||||
// vault shares or something else. Save it for finalize.
|
// vault shares or something else. Save it for finalize.
|
||||||
beforeMPTs_.push_back(Shares::make(*before));
|
beforeMPTs_.push_back(Shares::make(*before));
|
||||||
balanceDelta = static_cast<std::int64_t>(
|
balanceDelta.delta = static_cast<std::int64_t>(
|
||||||
before->getFieldU64(sfOutstandingAmount));
|
before->getFieldU64(sfOutstandingAmount));
|
||||||
|
// MPTs are ints, so the scale is always 0.
|
||||||
|
balanceDelta.scale = 0;
|
||||||
sign = 1;
|
sign = 1;
|
||||||
break;
|
break;
|
||||||
case ltMPTOKEN:
|
case ltMPTOKEN:
|
||||||
balanceDelta =
|
balanceDelta.delta =
|
||||||
static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
|
static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
|
||||||
|
// MPTs are ints, so the scale is always 0.
|
||||||
|
balanceDelta.scale = 0;
|
||||||
sign = -1;
|
sign = -1;
|
||||||
break;
|
break;
|
||||||
case ltACCOUNT_ROOT:
|
case ltACCOUNT_ROOT:
|
||||||
case ltRIPPLE_STATE:
|
balanceDelta.delta = before->getFieldAmount(sfBalance);
|
||||||
balanceDelta = before->getFieldAmount(sfBalance);
|
// Account balance is XRP, which is an int, so the scale is
|
||||||
|
// always 0.
|
||||||
|
balanceDelta.scale = 0;
|
||||||
sign = -1;
|
sign = -1;
|
||||||
break;
|
break;
|
||||||
|
case ltRIPPLE_STATE: {
|
||||||
|
auto const amount = before->getFieldAmount(sfBalance);
|
||||||
|
balanceDelta.delta = amount;
|
||||||
|
// Trust Line balances are STAmounts, so we can use the exponent
|
||||||
|
// directly to get the scale.
|
||||||
|
balanceDelta.scale = amount.exponent();
|
||||||
|
sign = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:;
|
default:;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2707,20 +2728,36 @@ ValidVault::visitEntry(
|
|||||||
// At this moment we have no way of telling if this object holds
|
// At this moment we have no way of telling if this object holds
|
||||||
// vault shares or something else. Save it for finalize.
|
// vault shares or something else. Save it for finalize.
|
||||||
afterMPTs_.push_back(Shares::make(*after));
|
afterMPTs_.push_back(Shares::make(*after));
|
||||||
balanceDelta -= Number(static_cast<std::int64_t>(
|
balanceDelta.delta -= Number(static_cast<std::int64_t>(
|
||||||
after->getFieldU64(sfOutstandingAmount)));
|
after->getFieldU64(sfOutstandingAmount)));
|
||||||
|
// MPTs are ints, so the scale is always 0.
|
||||||
|
balanceDelta.scale = 0;
|
||||||
sign = 1;
|
sign = 1;
|
||||||
break;
|
break;
|
||||||
case ltMPTOKEN:
|
case ltMPTOKEN:
|
||||||
balanceDelta -= Number(
|
balanceDelta.delta -= Number(
|
||||||
static_cast<std::int64_t>(after->getFieldU64(sfMPTAmount)));
|
static_cast<std::int64_t>(after->getFieldU64(sfMPTAmount)));
|
||||||
|
// MPTs are ints, so the scale is always 0.
|
||||||
|
balanceDelta.scale = 0;
|
||||||
sign = -1;
|
sign = -1;
|
||||||
break;
|
break;
|
||||||
case ltACCOUNT_ROOT:
|
case ltACCOUNT_ROOT:
|
||||||
case ltRIPPLE_STATE:
|
balanceDelta.delta -= Number(after->getFieldAmount(sfBalance));
|
||||||
balanceDelta -= Number(after->getFieldAmount(sfBalance));
|
// Account balance is XRP, which is an int, so the scale is
|
||||||
|
// always 0.
|
||||||
|
balanceDelta.scale = 0;
|
||||||
sign = -1;
|
sign = -1;
|
||||||
break;
|
break;
|
||||||
|
case ltRIPPLE_STATE: {
|
||||||
|
auto const amount = after->getFieldAmount(sfBalance);
|
||||||
|
balanceDelta.delta -= Number(amount);
|
||||||
|
// Trust Line balances are STAmounts, so we can use the exponent
|
||||||
|
// directly to get the scale.
|
||||||
|
if (amount.exponent() > balanceDelta.scale)
|
||||||
|
balanceDelta.scale = amount.exponent();
|
||||||
|
sign = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:;
|
default:;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2732,7 +2769,14 @@ ValidVault::visitEntry(
|
|||||||
// transferred to the account. We intentionally do not compare balanceDelta
|
// transferred to the account. We intentionally do not compare balanceDelta
|
||||||
// against zero, to avoid missing such updates.
|
// against zero, to avoid missing such updates.
|
||||||
if (sign != 0)
|
if (sign != 0)
|
||||||
deltas_[key] = balanceDelta * sign;
|
{
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
balanceDelta.scale,
|
||||||
|
"xrpl::ValidVault::visitEntry",
|
||||||
|
"scale initialized");
|
||||||
|
balanceDelta.delta *= sign;
|
||||||
|
deltas_[key] = balanceDelta;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -3012,13 +3056,15 @@ ValidVault::finalize(
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto const& vaultAsset = afterVault.asset;
|
auto const& vaultAsset = afterVault.asset;
|
||||||
auto const deltaAssets = [&](AccountID const& id) -> std::optional<Number> {
|
auto const deltaAssets =
|
||||||
|
[&](AccountID const& id) -> std::optional<DeltaInfo> {
|
||||||
auto const get = //
|
auto const get = //
|
||||||
[&](auto const& it, std::int8_t sign = 1) -> std::optional<Number> {
|
[&](auto const& it,
|
||||||
|
std::int8_t sign = 1) -> std::optional<DeltaInfo> {
|
||||||
if (it == deltas_.end())
|
if (it == deltas_.end())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
return it->second * sign;
|
return DeltaInfo{it->second.delta * sign, it->second.scale};
|
||||||
};
|
};
|
||||||
|
|
||||||
return std::visit(
|
return std::visit(
|
||||||
@@ -3039,7 +3085,7 @@ ValidVault::finalize(
|
|||||||
},
|
},
|
||||||
vaultAsset.value());
|
vaultAsset.value());
|
||||||
};
|
};
|
||||||
auto const deltaAssetsTxAccount = [&]() -> std::optional<Number> {
|
auto const deltaAssetsTxAccount = [&]() -> std::optional<DeltaInfo> {
|
||||||
auto ret = deltaAssets(tx[sfAccount]);
|
auto ret = deltaAssets(tx[sfAccount]);
|
||||||
// Nothing returned or not XRP transaction
|
// Nothing returned or not XRP transaction
|
||||||
if (!ret.has_value() || !vaultAsset.native())
|
if (!ret.has_value() || !vaultAsset.native())
|
||||||
@@ -3050,13 +3096,14 @@ ValidVault::finalize(
|
|||||||
delegate.has_value() && *delegate != tx[sfAccount])
|
delegate.has_value() && *delegate != tx[sfAccount])
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
*ret += fee.drops();
|
ret->delta += fee.drops();
|
||||||
if (*ret == zero)
|
if (ret->delta == zero)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
auto const deltaShares = [&](AccountID const& id) -> std::optional<Number> {
|
auto const deltaShares =
|
||||||
|
[&](AccountID const& id) -> std::optional<DeltaInfo> {
|
||||||
auto const it = [&]() {
|
auto const it = [&]() {
|
||||||
if (id == afterVault.pseudoId)
|
if (id == afterVault.pseudoId)
|
||||||
return deltas_.find(
|
return deltas_.find(
|
||||||
@@ -3064,7 +3111,7 @@ ValidVault::finalize(
|
|||||||
return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key);
|
return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key);
|
||||||
}();
|
}();
|
||||||
|
|
||||||
return it != deltas_.end() ? std::optional<Number>(it->second)
|
return it != deltas_.end() ? std::optional<DeltaInfo>(it->second)
|
||||||
: std::nullopt;
|
: std::nullopt;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3196,16 +3243,41 @@ ValidVault::finalize(
|
|||||||
"xrpl::ValidVault::finalize : deposit updated a vault");
|
"xrpl::ValidVault::finalize : deposit updated a vault");
|
||||||
auto const& beforeVault = beforeVault_[0];
|
auto const& beforeVault = beforeVault_[0];
|
||||||
|
|
||||||
auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
|
auto const maybeVaultDeltaAssets =
|
||||||
|
deltaAssets(afterVault.pseudoId);
|
||||||
if (!vaultDeltaAssets)
|
if (!maybeVaultDeltaAssets)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: deposit must change vault balance";
|
"Invariant failed: deposit must change vault balance";
|
||||||
return false; // That's all we can do
|
return false; // That's all we can do
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*vaultDeltaAssets > tx[sfAmount])
|
// Get the coarsest scale to round calculations to
|
||||||
|
DeltaInfo totalDelta{
|
||||||
|
afterVault.assetsTotal - beforeVault.assetsTotal,
|
||||||
|
std::max(
|
||||||
|
afterVault.assetsTotal.scale<STAmount>(vaultAsset),
|
||||||
|
beforeVault.assetsTotal.scale<STAmount>(vaultAsset))};
|
||||||
|
DeltaInfo availableDelta{
|
||||||
|
afterVault.assetsAvailable - beforeVault.assetsAvailable,
|
||||||
|
std::max(
|
||||||
|
afterVault.assetsAvailable.scale<STAmount>(vaultAsset),
|
||||||
|
beforeVault.assetsAvailable.scale<STAmount>(
|
||||||
|
vaultAsset))};
|
||||||
|
auto const minScale = computeMinScale(
|
||||||
|
vaultAsset,
|
||||||
|
{
|
||||||
|
*maybeVaultDeltaAssets,
|
||||||
|
totalDelta,
|
||||||
|
availableDelta,
|
||||||
|
});
|
||||||
|
|
||||||
|
auto const vaultDeltaAssets = roundToAsset(
|
||||||
|
vaultAsset, maybeVaultDeltaAssets->delta, minScale);
|
||||||
|
auto const txAmount =
|
||||||
|
roundToAsset(vaultAsset, tx[sfAmount], minScale);
|
||||||
|
|
||||||
|
if (vaultDeltaAssets > txAmount)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: deposit must not change vault "
|
"Invariant failed: deposit must not change vault "
|
||||||
@@ -3213,7 +3285,7 @@ ValidVault::finalize(
|
|||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*vaultDeltaAssets <= zero)
|
if (vaultDeltaAssets <= zero)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: deposit must increase vault balance";
|
"Invariant failed: deposit must increase vault balance";
|
||||||
@@ -3230,16 +3302,24 @@ ValidVault::finalize(
|
|||||||
|
|
||||||
if (!issuerDeposit)
|
if (!issuerDeposit)
|
||||||
{
|
{
|
||||||
auto const accountDeltaAssets = deltaAssetsTxAccount();
|
auto const maybeAccDeltaAssets = deltaAssetsTxAccount();
|
||||||
if (!accountDeltaAssets)
|
if (!maybeAccDeltaAssets)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: deposit must change depositor "
|
"Invariant failed: deposit must change depositor "
|
||||||
"balance";
|
"balance";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
auto const localMinScale = std::max(
|
||||||
|
minScale,
|
||||||
|
computeMinScale(vaultAsset, {*maybeAccDeltaAssets}));
|
||||||
|
|
||||||
if (*accountDeltaAssets >= zero)
|
auto const accountDeltaAssets = roundToAsset(
|
||||||
|
vaultAsset, maybeAccDeltaAssets->delta, localMinScale);
|
||||||
|
auto const localVaultDeltaAssets = roundToAsset(
|
||||||
|
vaultAsset, vaultDeltaAssets, localMinScale);
|
||||||
|
|
||||||
|
if (accountDeltaAssets >= zero)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: deposit must decrease depositor "
|
"Invariant failed: deposit must decrease depositor "
|
||||||
@@ -3247,7 +3327,7 @@ ValidVault::finalize(
|
|||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*accountDeltaAssets * -1 != *vaultDeltaAssets)
|
if (localVaultDeltaAssets * -1 != accountDeltaAssets)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: deposit must change vault and "
|
"Invariant failed: deposit must change vault and "
|
||||||
@@ -3265,16 +3345,17 @@ ValidVault::finalize(
|
|||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const accountDeltaShares = deltaShares(tx[sfAccount]);
|
auto const maybeAccDeltaShares = deltaShares(tx[sfAccount]);
|
||||||
if (!accountDeltaShares)
|
if (!maybeAccDeltaShares)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: deposit must change depositor "
|
"Invariant failed: deposit must change depositor "
|
||||||
"shares";
|
"shares";
|
||||||
return false; // That's all we can do
|
return false; // That's all we can do
|
||||||
}
|
}
|
||||||
|
// We don't need to round shares, they are integral MPT
|
||||||
if (*accountDeltaShares <= zero)
|
auto const& accountDeltaShares = *maybeAccDeltaShares;
|
||||||
|
if (accountDeltaShares.delta <= zero)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: deposit must increase depositor "
|
"Invariant failed: deposit must increase depositor "
|
||||||
@@ -3282,15 +3363,19 @@ ValidVault::finalize(
|
|||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
|
auto const maybeVaultDeltaShares =
|
||||||
if (!vaultDeltaShares || *vaultDeltaShares == zero)
|
deltaShares(afterVault.pseudoId);
|
||||||
|
if (!maybeVaultDeltaShares ||
|
||||||
|
maybeVaultDeltaShares->delta == zero)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: deposit must change vault shares";
|
"Invariant failed: deposit must change vault shares";
|
||||||
return false; // That's all we can do
|
return false; // That's all we can do
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*vaultDeltaShares * -1 != *accountDeltaShares)
|
// We don't need to round shares, they are integral MPT
|
||||||
|
auto const& vaultDeltaShares = *maybeVaultDeltaShares;
|
||||||
|
if (vaultDeltaShares.delta * -1 != accountDeltaShares.delta)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: deposit must change depositor and "
|
"Invariant failed: deposit must change depositor and "
|
||||||
@@ -3298,15 +3383,22 @@ ValidVault::finalize(
|
|||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (beforeVault.assetsTotal + *vaultDeltaAssets !=
|
auto const assetTotalDelta = roundToAsset(
|
||||||
afterVault.assetsTotal)
|
vaultAsset,
|
||||||
|
afterVault.assetsTotal - beforeVault.assetsTotal,
|
||||||
|
minScale);
|
||||||
|
if (assetTotalDelta != vaultDeltaAssets)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << "Invariant failed: deposit and assets "
|
JLOG(j.fatal()) << "Invariant failed: deposit and assets "
|
||||||
"outstanding must add up";
|
"outstanding must add up";
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
|
|
||||||
afterVault.assetsAvailable)
|
auto const assetAvailableDelta = roundToAsset(
|
||||||
|
vaultAsset,
|
||||||
|
afterVault.assetsAvailable - beforeVault.assetsAvailable,
|
||||||
|
minScale);
|
||||||
|
if (assetAvailableDelta != vaultDeltaAssets)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << "Invariant failed: deposit and assets "
|
JLOG(j.fatal()) << "Invariant failed: deposit and assets "
|
||||||
"available must add up";
|
"available must add up";
|
||||||
@@ -3324,22 +3416,41 @@ ValidVault::finalize(
|
|||||||
"vault");
|
"vault");
|
||||||
auto const& beforeVault = beforeVault_[0];
|
auto const& beforeVault = beforeVault_[0];
|
||||||
|
|
||||||
auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
|
auto const maybeVaultDeltaAssets =
|
||||||
|
deltaAssets(afterVault.pseudoId);
|
||||||
|
|
||||||
if (!vaultDeltaAssets)
|
if (!maybeVaultDeltaAssets)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << "Invariant failed: withdrawal must "
|
JLOG(j.fatal()) << "Invariant failed: withdrawal must "
|
||||||
"change vault balance";
|
"change vault balance";
|
||||||
return false; // That's all we can do
|
return false; // That's all we can do
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*vaultDeltaAssets >= zero)
|
// Get the most coarse scale to round calculations to
|
||||||
|
auto const totalDelta = DeltaInfo{
|
||||||
|
afterVault.assetsTotal - beforeVault.assetsTotal,
|
||||||
|
std::max(
|
||||||
|
afterVault.assetsTotal.scale<STAmount>(vaultAsset),
|
||||||
|
beforeVault.assetsTotal.scale<STAmount>(vaultAsset))};
|
||||||
|
auto const availableDelta = DeltaInfo{
|
||||||
|
afterVault.assetsAvailable - beforeVault.assetsAvailable,
|
||||||
|
std::max(
|
||||||
|
afterVault.assetsAvailable.scale<STAmount>(vaultAsset),
|
||||||
|
beforeVault.assetsAvailable.scale<STAmount>(
|
||||||
|
vaultAsset))};
|
||||||
|
auto const minScale = computeMinScale(
|
||||||
|
vaultAsset,
|
||||||
|
{*maybeVaultDeltaAssets, totalDelta, availableDelta});
|
||||||
|
|
||||||
|
auto const vaultPseudoDeltaAssets = roundToAsset(
|
||||||
|
vaultAsset, maybeVaultDeltaAssets->delta, minScale);
|
||||||
|
|
||||||
|
if (vaultPseudoDeltaAssets >= zero)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << "Invariant failed: withdrawal must "
|
JLOG(j.fatal()) << "Invariant failed: withdrawal must "
|
||||||
"decrease vault balance";
|
"decrease vault balance";
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any payments (including withdrawal) going to the issuer
|
// Any payments (including withdrawal) going to the issuer
|
||||||
// do not change their balance, but destroy funds instead.
|
// do not change their balance, but destroy funds instead.
|
||||||
bool const issuerWithdrawal = [&]() -> bool {
|
bool const issuerWithdrawal = [&]() -> bool {
|
||||||
@@ -3352,17 +3463,17 @@ ValidVault::finalize(
|
|||||||
|
|
||||||
if (!issuerWithdrawal)
|
if (!issuerWithdrawal)
|
||||||
{
|
{
|
||||||
auto const accountDeltaAssets = deltaAssetsTxAccount();
|
auto const maybeAccDelta = deltaAssetsTxAccount();
|
||||||
auto const otherAccountDelta =
|
auto const maybeOtherAccDelta =
|
||||||
[&]() -> std::optional<Number> {
|
[&]() -> std::optional<DeltaInfo> {
|
||||||
if (auto const destination = tx[~sfDestination];
|
if (auto const destination = tx[~sfDestination];
|
||||||
destination && *destination != tx[sfAccount])
|
destination && *destination != tx[sfAccount])
|
||||||
return deltaAssets(*destination);
|
return deltaAssets(*destination);
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
if (accountDeltaAssets.has_value() ==
|
if (maybeAccDelta.has_value() ==
|
||||||
otherAccountDelta.has_value())
|
maybeOtherAccDelta.has_value())
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: withdrawal must change one "
|
"Invariant failed: withdrawal must change one "
|
||||||
@@ -3371,10 +3482,18 @@ ValidVault::finalize(
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto const destinationDelta = //
|
auto const destinationDelta = //
|
||||||
accountDeltaAssets ? *accountDeltaAssets
|
maybeAccDelta ? *maybeAccDelta : *maybeOtherAccDelta;
|
||||||
: *otherAccountDelta;
|
|
||||||
|
|
||||||
if (destinationDelta <= zero)
|
// the scale of destinationDelta can be coarser than
|
||||||
|
// minScale, so we take that into account when rounding
|
||||||
|
auto const localMinScale = std::max(
|
||||||
|
minScale,
|
||||||
|
computeMinScale(vaultAsset, {destinationDelta}));
|
||||||
|
|
||||||
|
auto const roundedDestinationDelta = roundToAsset(
|
||||||
|
vaultAsset, destinationDelta.delta, localMinScale);
|
||||||
|
|
||||||
|
if (roundedDestinationDelta <= zero)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: withdrawal must increase "
|
"Invariant failed: withdrawal must increase "
|
||||||
@@ -3382,7 +3501,9 @@ ValidVault::finalize(
|
|||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*vaultDeltaAssets * -1 != destinationDelta)
|
auto const localPseudoDeltaAssets = roundToAsset(
|
||||||
|
vaultAsset, vaultPseudoDeltaAssets, localMinScale);
|
||||||
|
if (localPseudoDeltaAssets * -1 != roundedDestinationDelta)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: withdrawal must change vault "
|
"Invariant failed: withdrawal must change vault "
|
||||||
@@ -3390,7 +3511,7 @@ ValidVault::finalize(
|
|||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// We don't need to round shares, they are integral MPT
|
||||||
auto const accountDeltaShares = deltaShares(tx[sfAccount]);
|
auto const accountDeltaShares = deltaShares(tx[sfAccount]);
|
||||||
if (!accountDeltaShares)
|
if (!accountDeltaShares)
|
||||||
{
|
{
|
||||||
@@ -3400,23 +3521,23 @@ ValidVault::finalize(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*accountDeltaShares >= zero)
|
if (accountDeltaShares->delta >= zero)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: withdrawal must decrease depositor "
|
"Invariant failed: withdrawal must decrease depositor "
|
||||||
"shares";
|
"shares";
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
// We don't need to round shares, they are integral MPT
|
||||||
auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
|
auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
|
||||||
if (!vaultDeltaShares || *vaultDeltaShares == zero)
|
if (!vaultDeltaShares || vaultDeltaShares->delta == zero)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: withdrawal must change vault shares";
|
"Invariant failed: withdrawal must change vault shares";
|
||||||
return false; // That's all we can do
|
return false; // That's all we can do
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*vaultDeltaShares * -1 != *accountDeltaShares)
|
if (vaultDeltaShares->delta * -1 != accountDeltaShares->delta)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: withdrawal must change depositor "
|
"Invariant failed: withdrawal must change depositor "
|
||||||
@@ -3424,17 +3545,24 @@ ValidVault::finalize(
|
|||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto const assetTotalDelta = roundToAsset(
|
||||||
|
vaultAsset,
|
||||||
|
afterVault.assetsTotal - beforeVault.assetsTotal,
|
||||||
|
minScale);
|
||||||
// Note, vaultBalance is negative (see check above)
|
// Note, vaultBalance is negative (see check above)
|
||||||
if (beforeVault.assetsTotal + *vaultDeltaAssets !=
|
if (assetTotalDelta != vaultPseudoDeltaAssets)
|
||||||
afterVault.assetsTotal)
|
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << "Invariant failed: withdrawal and "
|
JLOG(j.fatal()) << "Invariant failed: withdrawal and "
|
||||||
"assets outstanding must add up";
|
"assets outstanding must add up";
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
|
auto const assetAvailableDelta = roundToAsset(
|
||||||
afterVault.assetsAvailable)
|
vaultAsset,
|
||||||
|
afterVault.assetsAvailable - beforeVault.assetsAvailable,
|
||||||
|
minScale);
|
||||||
|
|
||||||
|
if (assetAvailableDelta != vaultPseudoDeltaAssets)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << "Invariant failed: withdrawal and "
|
JLOG(j.fatal()) << "Invariant failed: withdrawal and "
|
||||||
"assets available must add up";
|
"assets available must add up";
|
||||||
@@ -3468,10 +3596,30 @@ ValidVault::finalize(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
|
auto const maybeVaultDeltaAssets =
|
||||||
if (vaultDeltaAssets)
|
deltaAssets(afterVault.pseudoId);
|
||||||
|
if (maybeVaultDeltaAssets)
|
||||||
{
|
{
|
||||||
if (*vaultDeltaAssets >= zero)
|
auto const totalDelta = DeltaInfo{
|
||||||
|
afterVault.assetsTotal - beforeVault.assetsTotal,
|
||||||
|
std::max(
|
||||||
|
afterVault.assetsTotal.scale<STAmount>(vaultAsset),
|
||||||
|
beforeVault.assetsTotal.scale<STAmount>(
|
||||||
|
vaultAsset))};
|
||||||
|
auto const availableDelta = DeltaInfo{
|
||||||
|
afterVault.assetsAvailable -
|
||||||
|
beforeVault.assetsAvailable,
|
||||||
|
std::max(
|
||||||
|
afterVault.assetsAvailable.scale<STAmount>(
|
||||||
|
vaultAsset),
|
||||||
|
beforeVault.assetsAvailable.scale<STAmount>(
|
||||||
|
vaultAsset))};
|
||||||
|
auto const minScale = computeMinScale(
|
||||||
|
vaultAsset,
|
||||||
|
{*maybeVaultDeltaAssets, totalDelta, availableDelta});
|
||||||
|
auto const vaultDeltaAssets = roundToAsset(
|
||||||
|
vaultAsset, maybeVaultDeltaAssets->delta, minScale);
|
||||||
|
if (vaultDeltaAssets >= zero)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: clawback must decrease vault "
|
"Invariant failed: clawback must decrease vault "
|
||||||
@@ -3479,8 +3627,11 @@ ValidVault::finalize(
|
|||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (beforeVault.assetsTotal + *vaultDeltaAssets !=
|
auto const assetsTotalDelta = roundToAsset(
|
||||||
afterVault.assetsTotal)
|
vaultAsset,
|
||||||
|
afterVault.assetsTotal - beforeVault.assetsTotal,
|
||||||
|
minScale);
|
||||||
|
if (assetsTotalDelta != vaultDeltaAssets)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: clawback and assets outstanding "
|
"Invariant failed: clawback and assets outstanding "
|
||||||
@@ -3488,8 +3639,12 @@ ValidVault::finalize(
|
|||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
|
auto const assetAvailableDelta = roundToAsset(
|
||||||
afterVault.assetsAvailable)
|
vaultAsset,
|
||||||
|
afterVault.assetsAvailable -
|
||||||
|
beforeVault.assetsAvailable,
|
||||||
|
minScale);
|
||||||
|
if (assetAvailableDelta != vaultDeltaAssets)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: clawback and assets available "
|
"Invariant failed: clawback and assets available "
|
||||||
@@ -3504,15 +3659,15 @@ ValidVault::finalize(
|
|||||||
return false; // That's all we can do
|
return false; // That's all we can do
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const accountDeltaShares = deltaShares(tx[sfHolder]);
|
// We don't need to round shares, they are integral MPT
|
||||||
if (!accountDeltaShares)
|
auto const maybeAccountDeltaShares = deltaShares(tx[sfHolder]);
|
||||||
|
if (!maybeAccountDeltaShares)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: clawback must change holder shares";
|
"Invariant failed: clawback must change holder shares";
|
||||||
return false; // That's all we can do
|
return false; // That's all we can do
|
||||||
}
|
}
|
||||||
|
if (maybeAccountDeltaShares->delta >= zero)
|
||||||
if (*accountDeltaShares >= zero)
|
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: clawback must decrease holder "
|
"Invariant failed: clawback must decrease holder "
|
||||||
@@ -3520,15 +3675,17 @@ ValidVault::finalize(
|
|||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't need to round shares, they are integral MPT
|
||||||
auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
|
auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
|
||||||
if (!vaultDeltaShares || *vaultDeltaShares == zero)
|
if (!vaultDeltaShares || vaultDeltaShares->delta == zero)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: clawback must change vault shares";
|
"Invariant failed: clawback must change vault shares";
|
||||||
return false; // That's all we can do
|
return false; // That's all we can do
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*vaultDeltaShares * -1 != *accountDeltaShares)
|
if (vaultDeltaShares->delta * -1 !=
|
||||||
|
maybeAccountDeltaShares->delta)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << //
|
JLOG(j.fatal()) << //
|
||||||
"Invariant failed: clawback must change holder and "
|
"Invariant failed: clawback must change holder and "
|
||||||
@@ -3566,4 +3723,23 @@ ValidVault::finalize(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::int32_t
|
||||||
|
ValidVault::computeMinScale(
|
||||||
|
Asset const& asset,
|
||||||
|
std::initializer_list<DeltaInfo const> numbers)
|
||||||
|
{
|
||||||
|
if (numbers.size() == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto const max = std::max_element(
|
||||||
|
numbers.begin(),
|
||||||
|
numbers.end(),
|
||||||
|
[](auto const& a, auto const& b) -> bool { return a.scale < b.scale; });
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
max->scale,
|
||||||
|
"xrpl::ValidVault::computeMinScale",
|
||||||
|
"scale set for destinationDelta");
|
||||||
|
return max->scale.value_or(STAmount::cMaxOffset);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace xrpl
|
} // namespace xrpl
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#include <xrpl/protocol/STTx.h>
|
#include <xrpl/protocol/STTx.h>
|
||||||
#include <xrpl/protocol/TER.h>
|
#include <xrpl/protocol/TER.h>
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
@@ -880,11 +879,19 @@ class ValidVault
|
|||||||
Shares static make(SLE const&);
|
Shares static make(SLE const&);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct DeltaInfo final
|
||||||
|
{
|
||||||
|
Number delta = numZero;
|
||||||
|
std::optional<int> scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
std::vector<Vault> afterVault_ = {};
|
std::vector<Vault> afterVault_ = {};
|
||||||
std::vector<Shares> afterMPTs_ = {};
|
std::vector<Shares> afterMPTs_ = {};
|
||||||
std::vector<Vault> beforeVault_ = {};
|
std::vector<Vault> beforeVault_ = {};
|
||||||
std::vector<Shares> beforeMPTs_ = {};
|
std::vector<Shares> beforeMPTs_ = {};
|
||||||
std::unordered_map<uint256, Number> deltas_ = {};
|
std::unordered_map<uint256, DeltaInfo> deltas_ = {};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void
|
void
|
||||||
@@ -900,6 +907,12 @@ public:
|
|||||||
XRPAmount const,
|
XRPAmount const,
|
||||||
ReadView const&,
|
ReadView const&,
|
||||||
beast::Journal const&);
|
beast::Journal const&);
|
||||||
|
|
||||||
|
// Compute the coarsest scale required to represent all numbers
|
||||||
|
[[nodiscard]] static std::int32_t
|
||||||
|
computeMinScale(
|
||||||
|
Asset const& asset,
|
||||||
|
std::initializer_list<DeltaInfo const> numbers);
|
||||||
};
|
};
|
||||||
|
|
||||||
// additional invariant checks can be declared above and then added to this
|
// additional invariant checks can be declared above and then added to this
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
|
|||||||
tenthBipsOfValue(
|
tenthBipsOfValue(
|
||||||
currentDebtTotal,
|
currentDebtTotal,
|
||||||
TenthBips32(sleBroker->at(sfCoverRateMinimum))),
|
TenthBips32(sleBroker->at(sfCoverRateMinimum))),
|
||||||
currentDebtTotal.exponent());
|
currentDebtTotal.scale<STAmount>(vaultAsset));
|
||||||
}();
|
}();
|
||||||
if (coverAvail < amount)
|
if (coverAvail < amount)
|
||||||
return tecINSUFFICIENT_FUNDS;
|
return tecINSUFFICIENT_FUNDS;
|
||||||
|
|||||||
Reference in New Issue
Block a user