mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Compare commits
28 Commits
bthomee/cc
...
ximinez/le
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21d66cbe78 | ||
|
|
3be46bc7d9 | ||
|
|
359622981e | ||
|
|
59e6a8107a | ||
|
|
92388c4491 | ||
|
|
e45d847558 | ||
|
|
546bfa89d8 | ||
|
|
470c9c3936 | ||
|
|
9f50cc033e | ||
|
|
a5f43fb59b | ||
|
|
3451d15e12 | ||
|
|
595a5ee220 | ||
|
|
c9ad49faf3 | ||
|
|
857eaffa55 | ||
|
|
93109918ed | ||
|
|
fbcd4f33eb | ||
|
|
6f1fe5047b | ||
|
|
4cf22b50de | ||
|
|
606e3ec0b7 | ||
|
|
2eca3dca89 | ||
|
|
2881aade2c | ||
|
|
4abb6d9dfe | ||
|
|
7cd48a7713 | ||
|
|
d2d403da90 | ||
|
|
343824332c | ||
|
|
a32b5723e5 | ||
|
|
3048f55270 | ||
|
|
d030fdaa2b |
@@ -1,8 +1,15 @@
|
|||||||
#ifndef XRPL_BASICS_NUMBER_H_INCLUDED
|
#ifndef XRPL_BASICS_NUMBER_H_INCLUDED
|
||||||
#define XRPL_BASICS_NUMBER_H_INCLUDED
|
#define XRPL_BASICS_NUMBER_H_INCLUDED
|
||||||
|
|
||||||
|
#include <xrpl/beast/utility/instrumentation.h>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <boost/multiprecision/cpp_int.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -13,31 +20,97 @@ class Number;
|
|||||||
std::string
|
std::string
|
||||||
to_string(Number const& amount);
|
to_string(Number const& amount);
|
||||||
|
|
||||||
class Number
|
template <typename T>
|
||||||
|
constexpr std::optional<int>
|
||||||
|
logTen(T value)
|
||||||
|
{
|
||||||
|
int log = 0;
|
||||||
|
while (value >= 10 && value % 10 == 0)
|
||||||
|
{
|
||||||
|
value /= 10;
|
||||||
|
++log;
|
||||||
|
}
|
||||||
|
if (value == 1)
|
||||||
|
return log;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
constexpr bool
|
||||||
|
isPowerOfTen(T value)
|
||||||
|
{
|
||||||
|
return logTen(value).has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
using numberuint = boost::multiprecision::uint128_t;
|
||||||
|
using numberint = boost::multiprecision::int128_t;
|
||||||
|
#else // !defined(_MSC_VER)
|
||||||
|
using numberuint = __uint128_t;
|
||||||
|
using numberint = __int128_t;
|
||||||
|
#endif // !defined(_MSC_VER)
|
||||||
|
|
||||||
|
struct MantissaRange
|
||||||
{
|
{
|
||||||
using rep = std::int64_t;
|
using rep = std::int64_t;
|
||||||
rep mantissa_{0};
|
using internalrep = numberint;
|
||||||
|
enum mantissa_scale { small, large };
|
||||||
|
|
||||||
|
explicit constexpr MantissaRange(mantissa_scale scale_, internalrep min_)
|
||||||
|
: min(min_)
|
||||||
|
, max(min_ * 10 - 1)
|
||||||
|
, log(logTen(min).value_or(-1))
|
||||||
|
, scale(scale_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
rep min;
|
||||||
|
internalrep max;
|
||||||
|
int log;
|
||||||
|
mantissa_scale scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Number
|
||||||
|
{
|
||||||
|
using uint128_t = numberuint;
|
||||||
|
using int128_t = numberint;
|
||||||
|
|
||||||
|
using rep = MantissaRange::rep;
|
||||||
|
using internalrep = MantissaRange::internalrep;
|
||||||
|
|
||||||
|
internalrep mantissa_{0};
|
||||||
int exponent_{std::numeric_limits<int>::lowest()};
|
int exponent_{std::numeric_limits<int>::lowest()};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// The range for the mantissa when normalized
|
|
||||||
constexpr static std::int64_t minMantissa = 1'000'000'000'000'000LL;
|
|
||||||
constexpr static std::int64_t maxMantissa = 9'999'999'999'999'999LL;
|
|
||||||
|
|
||||||
// The range for the exponent when normalized
|
// The range for the exponent when normalized
|
||||||
constexpr static int minExponent = -32768;
|
constexpr static int minExponent = -32768;
|
||||||
constexpr static int maxExponent = 32768;
|
constexpr static int maxExponent = 32768;
|
||||||
|
|
||||||
|
// May need to make unchecked private
|
||||||
struct unchecked
|
struct unchecked
|
||||||
{
|
{
|
||||||
explicit unchecked() = default;
|
explicit unchecked() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Like unchecked, normalized is used with the ctors that take an
|
||||||
|
// internalrep mantissa. Unlike unchecked, those ctors will normalize the
|
||||||
|
// value.
|
||||||
|
// Only unit tests are expected to use this class
|
||||||
|
struct normalized
|
||||||
|
{
|
||||||
|
explicit normalized() = default;
|
||||||
|
};
|
||||||
|
|
||||||
explicit constexpr Number() = default;
|
explicit constexpr Number() = default;
|
||||||
|
|
||||||
Number(rep mantissa);
|
Number(rep mantissa);
|
||||||
explicit Number(rep mantissa, int exponent);
|
explicit Number(rep mantissa, int exponent);
|
||||||
explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept;
|
explicit constexpr Number(
|
||||||
|
internalrep mantissa,
|
||||||
|
int exponent,
|
||||||
|
unchecked) noexcept;
|
||||||
|
// Only unit tests are expected to use this ctor
|
||||||
|
explicit Number(internalrep mantissa, int exponent, normalized);
|
||||||
|
|
||||||
constexpr rep
|
constexpr rep
|
||||||
mantissa() const noexcept;
|
mantissa() const noexcept;
|
||||||
@@ -67,11 +140,11 @@ public:
|
|||||||
Number&
|
Number&
|
||||||
operator/=(Number const& x);
|
operator/=(Number const& x);
|
||||||
|
|
||||||
static constexpr Number
|
static Number
|
||||||
min() noexcept;
|
min() noexcept;
|
||||||
static constexpr Number
|
static Number
|
||||||
max() noexcept;
|
max() noexcept;
|
||||||
static constexpr Number
|
static Number
|
||||||
lowest() noexcept;
|
lowest() noexcept;
|
||||||
|
|
||||||
/** Conversions to Number are implicit and conversions away from Number
|
/** Conversions to Number are implicit and conversions away from Number
|
||||||
@@ -141,7 +214,7 @@ public:
|
|||||||
while (ret.exponent_ < 0 && ret.mantissa_ != 0)
|
while (ret.exponent_ < 0 && ret.mantissa_ != 0)
|
||||||
{
|
{
|
||||||
ret.exponent_ += 1;
|
ret.exponent_ += 1;
|
||||||
ret.mantissa_ /= rep(10);
|
ret.mantissa_ /= internalrep(10);
|
||||||
}
|
}
|
||||||
// We are guaranteed that normalize() will never throw an exception
|
// We are guaranteed that normalize() will never throw an exception
|
||||||
// because exponent is either negative or zero at this point.
|
// because exponent is either negative or zero at this point.
|
||||||
@@ -173,6 +246,15 @@ public:
|
|||||||
return os << to_string(x);
|
return os << to_string(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
friend std::string
|
||||||
|
to_string(Number const& amount);
|
||||||
|
|
||||||
|
friend Number
|
||||||
|
root(Number f, unsigned d);
|
||||||
|
|
||||||
|
friend Number
|
||||||
|
root2(Number f);
|
||||||
|
|
||||||
// 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
|
||||||
@@ -181,24 +263,113 @@ public:
|
|||||||
static rounding_mode
|
static rounding_mode
|
||||||
setround(rounding_mode mode);
|
setround(rounding_mode mode);
|
||||||
|
|
||||||
|
static MantissaRange::mantissa_scale
|
||||||
|
getMantissaScale();
|
||||||
|
static void
|
||||||
|
setMantissaScale(MantissaRange::mantissa_scale scale);
|
||||||
|
|
||||||
|
template <class T = rep>
|
||||||
|
inline static T
|
||||||
|
minMantissa()
|
||||||
|
{
|
||||||
|
return range_.get().min;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static internalrep
|
||||||
|
maxMantissa()
|
||||||
|
{
|
||||||
|
return range_.get().max;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static int
|
||||||
|
mantissaLog()
|
||||||
|
{
|
||||||
|
return range_.get().log;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// oneSmall is needed because the ranges are private
|
||||||
|
constexpr static Number
|
||||||
|
oneSmall();
|
||||||
|
/// oneLarge is needed because the ranges are private
|
||||||
|
constexpr static Number
|
||||||
|
oneLarge();
|
||||||
|
|
||||||
|
// And one is needed because it needs to choose between oneSmall and
|
||||||
|
// oneLarge based on the current range
|
||||||
|
static Number
|
||||||
|
one();
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
[[nodiscard]]
|
||||||
|
std::pair<T, int>
|
||||||
|
normalizeToRange(T minMantissa, T maxMantissa) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static thread_local rounding_mode mode_;
|
static thread_local rounding_mode mode_;
|
||||||
|
// The available ranges for mantissa
|
||||||
|
|
||||||
|
constexpr static internalrep maxRep =
|
||||||
|
std::numeric_limits<std::int64_t>::max();
|
||||||
|
|
||||||
|
constexpr static MantissaRange smallRange{
|
||||||
|
MantissaRange::small,
|
||||||
|
1'000'000'000'000'000LL};
|
||||||
|
static_assert(isPowerOfTen(smallRange.min));
|
||||||
|
static_assert(smallRange.max == 9'999'999'999'999'999LL);
|
||||||
|
static_assert(smallRange.log == 15);
|
||||||
|
constexpr static MantissaRange largeRange{
|
||||||
|
MantissaRange::large,
|
||||||
|
1'000'000'000'000'000'000LL};
|
||||||
|
static_assert(isPowerOfTen(largeRange.min));
|
||||||
|
// maxRep 9,223,372,036,854,775,808
|
||||||
|
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL));
|
||||||
|
static_assert(largeRange.log == 18);
|
||||||
|
static_assert(largeRange.min < maxRep);
|
||||||
|
static_assert(largeRange.max > maxRep);
|
||||||
|
|
||||||
|
// The range for the mantissa when normalized.
|
||||||
|
// Use reference_wrapper to avoid making copies, and prevent accidentally
|
||||||
|
// changing the values inside the range.
|
||||||
|
static thread_local std::reference_wrapper<MantissaRange const> range_;
|
||||||
|
|
||||||
void
|
void
|
||||||
normalize();
|
normalize();
|
||||||
constexpr bool
|
|
||||||
|
static void
|
||||||
|
normalize(
|
||||||
|
internalrep& mantissa,
|
||||||
|
int& exponent,
|
||||||
|
internalrep const& minMantissa,
|
||||||
|
internalrep const& maxMantissa);
|
||||||
|
|
||||||
|
bool
|
||||||
isnormal() const noexcept;
|
isnormal() const noexcept;
|
||||||
|
|
||||||
|
// Copy the number, but modify the exponent by "exponentDelta". Because the
|
||||||
|
// mantissa doesn't change, the result will be "mostly" normalized, but the
|
||||||
|
// exponent could go out of range, so it will be checked.
|
||||||
|
Number
|
||||||
|
shiftExponent(int exponentDelta) const;
|
||||||
|
|
||||||
class Guard;
|
class Guard;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline constexpr Number::Number(rep mantissa, int exponent, unchecked) noexcept
|
inline constexpr Number::Number(
|
||||||
|
internalrep mantissa,
|
||||||
|
int exponent,
|
||||||
|
unchecked) noexcept
|
||||||
: mantissa_{mantissa}, exponent_{exponent}
|
: mantissa_{mantissa}, exponent_{exponent}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Number::Number(internalrep mantissa, int exponent, normalized)
|
||||||
|
: Number(mantissa, exponent, unchecked{})
|
||||||
|
{
|
||||||
|
normalize();
|
||||||
|
}
|
||||||
|
|
||||||
inline Number::Number(rep mantissa, int exponent)
|
inline Number::Number(rep mantissa, int exponent)
|
||||||
: mantissa_{mantissa}, exponent_{exponent}
|
: Number(mantissa, exponent, normalized{})
|
||||||
{
|
{
|
||||||
normalize();
|
normalize();
|
||||||
}
|
}
|
||||||
@@ -210,13 +381,33 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
|
|||||||
inline constexpr Number::rep
|
inline constexpr Number::rep
|
||||||
Number::mantissa() const noexcept
|
Number::mantissa() const noexcept
|
||||||
{
|
{
|
||||||
return mantissa_;
|
auto m = mantissa_;
|
||||||
|
while (m > maxRep)
|
||||||
|
{
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
!isnormal() || m % 10 == 0,
|
||||||
|
"ripple::Number::mantissa",
|
||||||
|
"large normalized mantissa has no remainder");
|
||||||
|
m /= 10;
|
||||||
|
}
|
||||||
|
return static_cast<Number::rep>(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline constexpr int
|
inline constexpr int
|
||||||
Number::exponent() const noexcept
|
Number::exponent() const noexcept
|
||||||
{
|
{
|
||||||
return exponent_;
|
auto m = mantissa_;
|
||||||
|
auto e = exponent_;
|
||||||
|
while (m > maxRep)
|
||||||
|
{
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
!isnormal() || m % 10 == 0,
|
||||||
|
"ripple::Number::exponent",
|
||||||
|
"large normalized mantissa has no remainder");
|
||||||
|
m /= 10;
|
||||||
|
++e;
|
||||||
|
}
|
||||||
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline constexpr Number
|
inline constexpr Number
|
||||||
@@ -236,7 +427,7 @@ Number::operator-() const noexcept
|
|||||||
inline Number&
|
inline Number&
|
||||||
Number::operator++()
|
Number::operator++()
|
||||||
{
|
{
|
||||||
*this += Number{1000000000000000, -15, unchecked{}};
|
*this += one();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +442,7 @@ Number::operator++(int)
|
|||||||
inline Number&
|
inline Number&
|
||||||
Number::operator--()
|
Number::operator--()
|
||||||
{
|
{
|
||||||
*this -= Number{1000000000000000, -15, unchecked{}};
|
*this -= one();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,32 +492,45 @@ operator/(Number const& x, Number const& y)
|
|||||||
return z;
|
return z;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline constexpr Number
|
inline Number
|
||||||
Number::min() noexcept
|
Number::min() noexcept
|
||||||
{
|
{
|
||||||
return Number{minMantissa, minExponent, unchecked{}};
|
return Number{range_.get().min, minExponent, unchecked{}};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline constexpr Number
|
inline Number
|
||||||
Number::max() noexcept
|
Number::max() noexcept
|
||||||
{
|
{
|
||||||
return Number{maxMantissa, maxExponent, unchecked{}};
|
return Number{range_.get().max, maxExponent, unchecked{}};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline constexpr Number
|
inline Number
|
||||||
Number::lowest() noexcept
|
Number::lowest() noexcept
|
||||||
{
|
{
|
||||||
return -Number{maxMantissa, maxExponent, unchecked{}};
|
return -Number{range_.get().max, maxExponent, unchecked{}};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline constexpr bool
|
inline bool
|
||||||
Number::isnormal() const noexcept
|
Number::isnormal() const noexcept
|
||||||
{
|
{
|
||||||
|
MantissaRange const& range = range_;
|
||||||
auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_;
|
auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_;
|
||||||
return minMantissa <= abs_m && abs_m <= maxMantissa &&
|
return range.min <= abs_m && abs_m <= range.max &&
|
||||||
|
(abs_m <= maxRep || abs_m % 10 == 0) &&
|
||||||
minExponent <= exponent_ && exponent_ <= maxExponent;
|
minExponent <= exponent_ && exponent_ <= maxExponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
std::pair<T, int>
|
||||||
|
Number::normalizeToRange(T minMantissa, T maxMantissa) const
|
||||||
|
{
|
||||||
|
internalrep mantissa = mantissa_;
|
||||||
|
int exponent = exponent_;
|
||||||
|
Number::normalize(mantissa, exponent, minMantissa, maxMantissa);
|
||||||
|
|
||||||
|
return std::make_pair(static_cast<T>(mantissa), exponent);
|
||||||
|
}
|
||||||
|
|
||||||
inline constexpr Number
|
inline constexpr Number
|
||||||
abs(Number x) noexcept
|
abs(Number x) noexcept
|
||||||
{
|
{
|
||||||
@@ -366,6 +570,20 @@ squelch(Number const& x, Number const& limit) noexcept
|
|||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::string
|
||||||
|
to_string(MantissaRange::mantissa_scale const& scale)
|
||||||
|
{
|
||||||
|
switch (scale)
|
||||||
|
{
|
||||||
|
case MantissaRange::small:
|
||||||
|
return "small";
|
||||||
|
case MantissaRange::large:
|
||||||
|
return "large";
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Bad scale");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class saveNumberRoundMode
|
class saveNumberRoundMode
|
||||||
{
|
{
|
||||||
Number::rounding_mode mode_;
|
Number::rounding_mode mode_;
|
||||||
@@ -404,6 +622,33 @@ public:
|
|||||||
operator=(NumberRoundModeGuard const&) = delete;
|
operator=(NumberRoundModeGuard const&) = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sets the new scale and restores the old scale when it leaves scope. Since
|
||||||
|
// Number doesn't have that facility, we'll build it here.
|
||||||
|
//
|
||||||
|
// This class may only end up needed in tests
|
||||||
|
class NumberMantissaScaleGuard
|
||||||
|
{
|
||||||
|
MantissaRange::mantissa_scale saved_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NumberMantissaScaleGuard(
|
||||||
|
MantissaRange::mantissa_scale scale) noexcept
|
||||||
|
: saved_{Number::getMantissaScale()}
|
||||||
|
{
|
||||||
|
Number::setMantissaScale(scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
~NumberMantissaScaleGuard()
|
||||||
|
{
|
||||||
|
Number::setMantissaScale(saved_);
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberMantissaScaleGuard(NumberMantissaScaleGuard const&) = delete;
|
||||||
|
|
||||||
|
NumberMantissaScaleGuard&
|
||||||
|
operator=(NumberMantissaScaleGuard const&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
#endif // XRPL_BASICS_NUMBER_H_INCLUDED
|
#endif // XRPL_BASICS_NUMBER_H_INCLUDED
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ toAmount(
|
|||||||
{
|
{
|
||||||
if (isXRP(issue))
|
if (isXRP(issue))
|
||||||
return STAmount(issue, static_cast<std::int64_t>(n));
|
return STAmount(issue, static_cast<std::int64_t>(n));
|
||||||
return STAmount(issue, n.mantissa(), n.exponent());
|
return STAmount(issue, n);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -84,6 +84,19 @@ public:
|
|||||||
return holds<Issue>() && get<Issue>().native();
|
return holds<Issue>() && get<Issue>().native();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
integral() const
|
||||||
|
{
|
||||||
|
return std::visit(
|
||||||
|
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||||
|
if constexpr (std::is_same_v<TIss, Issue>)
|
||||||
|
return issue.native();
|
||||||
|
if constexpr (std::is_same_v<TIss, MPTIssue>)
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
issue_);
|
||||||
|
}
|
||||||
|
|
||||||
friend constexpr bool
|
friend constexpr bool
|
||||||
operator==(Asset const& lhs, Asset const& rhs);
|
operator==(Asset const& lhs, Asset const& rhs);
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ class IOUAmount : private boost::totally_ordered<IOUAmount>,
|
|||||||
private boost::additive<IOUAmount>
|
private boost::additive<IOUAmount>
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
std::int64_t mantissa_;
|
using mantissa_type = std::int64_t;
|
||||||
int exponent_;
|
using exponent_type = int;
|
||||||
|
mantissa_type mantissa_;
|
||||||
|
exponent_type exponent_;
|
||||||
|
|
||||||
/** Adjusts the mantissa and exponent to the proper range.
|
/** Adjusts the mantissa and exponent to the proper range.
|
||||||
|
|
||||||
@@ -38,11 +40,19 @@ private:
|
|||||||
void
|
void
|
||||||
normalize();
|
normalize();
|
||||||
|
|
||||||
|
IOUAmount(std::pair<mantissa_type, exponent_type> parts)
|
||||||
|
: IOUAmount(parts.first, parts.second)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::pair<mantissa_type, exponent_type>
|
||||||
|
scaleNumber(Number const& number);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
IOUAmount() = default;
|
IOUAmount() = default;
|
||||||
explicit IOUAmount(Number const& other);
|
explicit IOUAmount(Number const& other);
|
||||||
IOUAmount(beast::Zero);
|
IOUAmount(beast::Zero);
|
||||||
IOUAmount(std::int64_t mantissa, int exponent);
|
IOUAmount(mantissa_type mantissa, exponent_type exponent);
|
||||||
|
|
||||||
IOUAmount& operator=(beast::Zero);
|
IOUAmount& operator=(beast::Zero);
|
||||||
|
|
||||||
@@ -71,10 +81,10 @@ public:
|
|||||||
int
|
int
|
||||||
signum() const noexcept;
|
signum() const noexcept;
|
||||||
|
|
||||||
int
|
exponent_type
|
||||||
exponent() const noexcept;
|
exponent() const noexcept;
|
||||||
|
|
||||||
std::int64_t
|
mantissa_type
|
||||||
mantissa() const noexcept;
|
mantissa() const noexcept;
|
||||||
|
|
||||||
static IOUAmount
|
static IOUAmount
|
||||||
@@ -92,7 +102,7 @@ inline IOUAmount::IOUAmount(beast::Zero)
|
|||||||
*this = beast::zero;
|
*this = beast::zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline IOUAmount::IOUAmount(std::int64_t mantissa, int exponent)
|
inline IOUAmount::IOUAmount(mantissa_type mantissa, exponent_type exponent)
|
||||||
: mantissa_(mantissa), exponent_(exponent)
|
: mantissa_(mantissa), exponent_(exponent)
|
||||||
{
|
{
|
||||||
normalize();
|
normalize();
|
||||||
@@ -149,13 +159,13 @@ IOUAmount::signum() const noexcept
|
|||||||
return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0);
|
return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int
|
inline IOUAmount::exponent_type
|
||||||
IOUAmount::exponent() const noexcept
|
IOUAmount::exponent() const noexcept
|
||||||
{
|
{
|
||||||
return exponent_;
|
return exponent_;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::int64_t
|
inline IOUAmount::mantissa_type
|
||||||
IOUAmount::mantissa() const noexcept
|
IOUAmount::mantissa() const noexcept
|
||||||
{
|
{
|
||||||
return mantissa_;
|
return mantissa_;
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ public:
|
|||||||
bool
|
bool
|
||||||
native() const;
|
native() const;
|
||||||
|
|
||||||
|
bool
|
||||||
|
integral() const;
|
||||||
|
|
||||||
friend constexpr std::weak_ordering
|
friend constexpr std::weak_ordering
|
||||||
operator<=>(Issue const& lhs, Issue const& rhs);
|
operator<=>(Issue const& lhs, Issue const& rhs);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ public:
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
integral() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr bool
|
constexpr bool
|
||||||
|
|||||||
@@ -47,9 +47,11 @@ public:
|
|||||||
static int const cMaxOffset = 80;
|
static int const cMaxOffset = 80;
|
||||||
|
|
||||||
// Maximum native value supported by the code
|
// Maximum native value supported by the code
|
||||||
static std::uint64_t const cMinValue = 1000000000000000ull;
|
constexpr static std::uint64_t cMinValue = 1'000'000'000'000'000ull;
|
||||||
static std::uint64_t const cMaxValue = 9999999999999999ull;
|
static_assert(isPowerOfTen(cMinValue));
|
||||||
static std::uint64_t const cMaxNative = 9000000000000000000ull;
|
constexpr static std::uint64_t cMaxValue = cMinValue * 10 - 1;
|
||||||
|
static_assert(cMaxValue == 9'999'999'999'999'999ull);
|
||||||
|
static std::uint64_t const cMaxNative = 9'000'000'000'000'000'000ull;
|
||||||
|
|
||||||
// Max native value on network.
|
// Max native value on network.
|
||||||
static std::uint64_t const cMaxNativeN = 100000000000000000ull;
|
static std::uint64_t const cMaxNativeN = 100000000000000000ull;
|
||||||
@@ -136,7 +138,7 @@ public:
|
|||||||
|
|
||||||
template <AssetType A>
|
template <AssetType A>
|
||||||
STAmount(A const& asset, Number const& number)
|
STAmount(A const& asset, Number const& number)
|
||||||
: STAmount(asset, number.mantissa(), number.exponent())
|
: STAmount(asset, scaleNumber(asset, number))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +157,9 @@ public:
|
|||||||
int
|
int
|
||||||
exponent() const noexcept;
|
exponent() const noexcept;
|
||||||
|
|
||||||
|
bool
|
||||||
|
integral() const noexcept;
|
||||||
|
|
||||||
bool
|
bool
|
||||||
native() const noexcept;
|
native() const noexcept;
|
||||||
|
|
||||||
@@ -277,6 +282,22 @@ public:
|
|||||||
mpt() const;
|
mpt() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
template <AssetType A>
|
||||||
|
STAmount(
|
||||||
|
A const& asset,
|
||||||
|
std::tuple<mantissa_type, exponent_type, bool> parts)
|
||||||
|
: STAmount(
|
||||||
|
asset,
|
||||||
|
std::get<mantissa_type>(parts),
|
||||||
|
std::get<exponent_type>(parts),
|
||||||
|
std::get<bool>(parts))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <AssetType A>
|
||||||
|
static std::tuple<mantissa_type, exponent_type, bool>
|
||||||
|
scaleNumber(A const& asset, Number const& number);
|
||||||
|
|
||||||
static std::unique_ptr<STAmount>
|
static std::unique_ptr<STAmount>
|
||||||
construct(SerialIter&, SField const& name);
|
construct(SerialIter&, SField const& name);
|
||||||
|
|
||||||
@@ -340,10 +361,19 @@ STAmount::STAmount(
|
|||||||
, mIsNegative(negative)
|
, mIsNegative(negative)
|
||||||
{
|
{
|
||||||
// mValue is uint64, but needs to fit in the range of int64
|
// mValue is uint64, but needs to fit in the range of int64
|
||||||
XRPL_ASSERT(
|
if (Number::getMantissaScale() == MantissaRange::small)
|
||||||
mValue <= std::numeric_limits<std::int64_t>::max(),
|
{
|
||||||
"ripple::STAmount::STAmount(SField, A, std::uint64_t, int, bool) : "
|
XRPL_ASSERT(
|
||||||
"maximum mantissa input");
|
mValue <= std::numeric_limits<std::int64_t>::max(),
|
||||||
|
"ripple::STAmount::STAmount(SField, A, std::uint64_t, int, bool) : "
|
||||||
|
"maximum mantissa input");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (integral() && mValue > std::numeric_limits<std::int64_t>::max())
|
||||||
|
throw std::overflow_error(
|
||||||
|
"STAmount mantissa is too large " + std::to_string(mantissa));
|
||||||
|
}
|
||||||
canonicalize();
|
canonicalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,6 +465,12 @@ STAmount::exponent() const noexcept
|
|||||||
return mOffset;
|
return mOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
STAmount::integral() const noexcept
|
||||||
|
{
|
||||||
|
return mAsset.integral();
|
||||||
|
}
|
||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
STAmount::native() const noexcept
|
STAmount::native() const noexcept
|
||||||
{
|
{
|
||||||
@@ -531,12 +567,29 @@ STAmount::operator=(XRPAmount const& amount)
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <AssetType A>
|
||||||
|
inline std::tuple<STAmount::mantissa_type, STAmount::exponent_type, bool>
|
||||||
|
STAmount::scaleNumber(A const& asset, Number const& number)
|
||||||
|
{
|
||||||
|
bool const negative = number.mantissa() < 0;
|
||||||
|
Number const working{negative ? -number : number};
|
||||||
|
if (asset.integral())
|
||||||
|
{
|
||||||
|
return std::make_tuple(std::int64_t(working), 0, negative);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto const [mantissa, exponent] =
|
||||||
|
working.normalizeToRange(cMinValue, cMaxValue);
|
||||||
|
|
||||||
|
return std::make_tuple(mantissa, exponent, negative);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inline STAmount&
|
inline STAmount&
|
||||||
STAmount::operator=(Number const& number)
|
STAmount::operator=(Number const& number)
|
||||||
{
|
{
|
||||||
mIsNegative = number.mantissa() < 0;
|
std::tie(mValue, mOffset, mIsNegative) = scaleNumber(mAsset, number);
|
||||||
mValue = mIsNegative ? -number.mantissa() : number.mantissa();
|
|
||||||
mOffset = number.exponent();
|
|
||||||
canonicalize();
|
canonicalize();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -553,7 +606,7 @@ STAmount::clear()
|
|||||||
{
|
{
|
||||||
// The -100 is used to allow 0 to sort less than a small positive values
|
// The -100 is used to allow 0 to sort less than a small positive values
|
||||||
// which have a negative exponent.
|
// which have a negative exponent.
|
||||||
mOffset = native() ? 0 : -100;
|
mOffset = integral() ? 0 : -100;
|
||||||
mValue = 0;
|
mValue = 0;
|
||||||
mIsNegative = false;
|
mIsNegative = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -482,6 +482,8 @@ public:
|
|||||||
value_type
|
value_type
|
||||||
operator*() const;
|
operator*() const;
|
||||||
|
|
||||||
|
/// Do not use operator->() unless the field is required, or you've checked
|
||||||
|
/// that it's set.
|
||||||
T const*
|
T const*
|
||||||
operator->() const;
|
operator->() const;
|
||||||
|
|
||||||
@@ -718,6 +720,8 @@ STObject::Proxy<T>::operator*() const -> value_type
|
|||||||
return this->value();
|
return this->value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Do not use operator->() unless the field is required, or you've checked that
|
||||||
|
/// it's set.
|
||||||
template <class T>
|
template <class T>
|
||||||
T const*
|
T const*
|
||||||
STObject::Proxy<T>::operator->() const
|
STObject::Proxy<T>::operator->() const
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ systemName()
|
|||||||
|
|
||||||
/** Number of drops in the genesis account. */
|
/** Number of drops in the genesis account. */
|
||||||
constexpr XRPAmount INITIAL_XRP{100'000'000'000 * DROPS_PER_XRP};
|
constexpr XRPAmount INITIAL_XRP{100'000'000'000 * DROPS_PER_XRP};
|
||||||
|
static_assert(INITIAL_XRP.drops() == 100'000'000'000'000'000);
|
||||||
|
|
||||||
/** Returns true if the amount does not exceed the initial XRP in existence. */
|
/** Returns true if the amount does not exceed the initial XRP in existence. */
|
||||||
inline bool
|
inline bool
|
||||||
|
|||||||
@@ -479,10 +479,10 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
|
|||||||
{sfAccount, soeREQUIRED},
|
{sfAccount, soeREQUIRED},
|
||||||
{sfData, soeOPTIONAL},
|
{sfData, soeOPTIONAL},
|
||||||
{sfAsset, soeREQUIRED},
|
{sfAsset, soeREQUIRED},
|
||||||
{sfAssetsTotal, soeREQUIRED},
|
{sfAssetsTotal, soeDEFAULT},
|
||||||
{sfAssetsAvailable, soeREQUIRED},
|
{sfAssetsAvailable, soeDEFAULT},
|
||||||
{sfAssetsMaximum, soeDEFAULT},
|
{sfAssetsMaximum, soeDEFAULT},
|
||||||
{sfLossUnrealized, soeREQUIRED},
|
{sfLossUnrealized, soeDEFAULT},
|
||||||
{sfShareMPTID, soeREQUIRED},
|
{sfShareMPTID, soeREQUIRED},
|
||||||
{sfWithdrawalPolicy, soeREQUIRED},
|
{sfWithdrawalPolicy, soeREQUIRED},
|
||||||
{sfScale, soeDEFAULT},
|
{sfScale, soeDEFAULT},
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include <xrpl/basics/Number.h>
|
#include <xrpl/basics/Number.h>
|
||||||
|
//
|
||||||
|
#include <xrpl/basics/contract.h>
|
||||||
#include <xrpl/beast/utility/instrumentation.h>
|
#include <xrpl/beast/utility/instrumentation.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -19,10 +21,23 @@ using uint128_t = boost::multiprecision::uint128_t;
|
|||||||
#else // !defined(_MSC_VER)
|
#else // !defined(_MSC_VER)
|
||||||
using uint128_t = __uint128_t;
|
using uint128_t = __uint128_t;
|
||||||
#endif // !defined(_MSC_VER)
|
#endif // !defined(_MSC_VER)
|
||||||
|
static_assert(std::is_same_v<uint128_t, ripple::numberuint>);
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct make_unsigned<ripple::numberint>
|
||||||
|
{
|
||||||
|
using type = uint128_t;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace std
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
thread_local Number::rounding_mode Number::mode_ = Number::to_nearest;
|
thread_local Number::rounding_mode Number::mode_ = Number::to_nearest;
|
||||||
|
thread_local std::reference_wrapper<MantissaRange const> Number::range_ =
|
||||||
|
largeRange;
|
||||||
|
|
||||||
Number::rounding_mode
|
Number::rounding_mode
|
||||||
Number::getround()
|
Number::getround()
|
||||||
@@ -36,6 +51,21 @@ Number::setround(rounding_mode mode)
|
|||||||
return std::exchange(mode_, mode);
|
return std::exchange(mode_, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MantissaRange::mantissa_scale
|
||||||
|
Number::getMantissaScale()
|
||||||
|
{
|
||||||
|
return range_.get().scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Number::setMantissaScale(MantissaRange::mantissa_scale scale)
|
||||||
|
{
|
||||||
|
// scale_ and range_ MUST stay in lockstep
|
||||||
|
if (scale != MantissaRange::small && scale != MantissaRange::large)
|
||||||
|
LogicError("Unknown mantissa scale");
|
||||||
|
range_ = scale == MantissaRange::small ? smallRange : largeRange;
|
||||||
|
}
|
||||||
|
|
||||||
// Guard
|
// Guard
|
||||||
|
|
||||||
// The Guard class is used to tempoarily add extra digits of
|
// The Guard class is used to tempoarily add extra digits of
|
||||||
@@ -62,8 +92,9 @@ public:
|
|||||||
is_negative() const noexcept;
|
is_negative() const noexcept;
|
||||||
|
|
||||||
// add a digit
|
// add a digit
|
||||||
|
template <class T>
|
||||||
void
|
void
|
||||||
push(unsigned d) noexcept;
|
push(T d) noexcept;
|
||||||
|
|
||||||
// recover a digit
|
// recover a digit
|
||||||
unsigned
|
unsigned
|
||||||
@@ -74,6 +105,10 @@ public:
|
|||||||
// tie, round towards even.
|
// tie, round towards even.
|
||||||
int
|
int
|
||||||
round() noexcept;
|
round() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void
|
||||||
|
doPush(unsigned d) noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
@@ -95,13 +130,20 @@ Number::Guard::is_negative() const noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
Number::Guard::push(unsigned d) noexcept
|
Number::Guard::doPush(unsigned d) noexcept
|
||||||
{
|
{
|
||||||
xbit_ = xbit_ || (digits_ & 0x0000'0000'0000'000F) != 0;
|
xbit_ = xbit_ || (digits_ & 0x0000'0000'0000'000F) != 0;
|
||||||
digits_ >>= 4;
|
digits_ >>= 4;
|
||||||
digits_ |= (d & 0x0000'0000'0000'000FULL) << 60;
|
digits_ |= (d & 0x0000'0000'0000'000FULL) << 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
inline void
|
||||||
|
Number::Guard::push(T d) noexcept
|
||||||
|
{
|
||||||
|
doPush(static_cast<unsigned>(d));
|
||||||
|
}
|
||||||
|
|
||||||
inline unsigned
|
inline unsigned
|
||||||
Number::Guard::pop() noexcept
|
Number::Guard::pop() noexcept
|
||||||
{
|
{
|
||||||
@@ -153,20 +195,51 @@ Number::Guard::round() noexcept
|
|||||||
|
|
||||||
// Number
|
// Number
|
||||||
|
|
||||||
constexpr Number one{1000000000000000, -15, Number::unchecked{}};
|
constexpr Number
|
||||||
|
Number::oneSmall()
|
||||||
void
|
|
||||||
Number::normalize()
|
|
||||||
{
|
{
|
||||||
|
return Number{
|
||||||
|
Number::smallRange.min, -Number::smallRange.log, Number::unchecked{}};
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr Number oneSml = Number::oneSmall();
|
||||||
|
|
||||||
|
constexpr Number
|
||||||
|
Number::oneLarge()
|
||||||
|
{
|
||||||
|
return Number{
|
||||||
|
Number::largeRange.min, -Number::largeRange.log, Number::unchecked{}};
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr Number oneLrg = Number::oneLarge();
|
||||||
|
|
||||||
|
Number
|
||||||
|
Number::one()
|
||||||
|
{
|
||||||
|
if (&range_.get() == &smallRange)
|
||||||
|
return oneSml;
|
||||||
|
XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_");
|
||||||
|
return oneLrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the member names in this static function for now so the diff is cleaner
|
||||||
|
void
|
||||||
|
Number::normalize(
|
||||||
|
internalrep& mantissa_,
|
||||||
|
int& exponent_,
|
||||||
|
internalrep const& minMantissa,
|
||||||
|
internalrep const& maxMantissa)
|
||||||
|
{
|
||||||
|
constexpr Number zero = Number{};
|
||||||
if (mantissa_ == 0)
|
if (mantissa_ == 0)
|
||||||
{
|
{
|
||||||
*this = Number{};
|
mantissa_ = zero.mantissa_;
|
||||||
|
exponent_ = zero.exponent_;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bool const negative = (mantissa_ < 0);
|
bool const negative = (mantissa_ < 0);
|
||||||
auto m = static_cast<std::make_unsigned_t<rep>>(mantissa_);
|
auto m = static_cast<std::make_unsigned_t<internalrep>>(
|
||||||
if (negative)
|
negative ? -mantissa_ : mantissa_);
|
||||||
m = -m;
|
|
||||||
while ((m < minMantissa) && (exponent_ > minExponent))
|
while ((m < minMantissa) && (exponent_ > minExponent))
|
||||||
{
|
{
|
||||||
m *= 10;
|
m *= 10;
|
||||||
@@ -183,12 +256,37 @@ Number::normalize()
|
|||||||
m /= 10;
|
m /= 10;
|
||||||
++exponent_;
|
++exponent_;
|
||||||
}
|
}
|
||||||
mantissa_ = m;
|
if ((exponent_ < minExponent) || (m < minMantissa))
|
||||||
if ((exponent_ < minExponent) || (mantissa_ < minMantissa))
|
|
||||||
{
|
{
|
||||||
*this = Number{};
|
mantissa_ = zero.mantissa_;
|
||||||
|
exponent_ = zero.exponent_;
|
||||||
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 range.
|
||||||
|
// Cut it down here so that the rounding will be done while it's smaller.
|
||||||
|
//
|
||||||
|
// Example: 9,900,000,000,000,555,555 > 9,223,372,036,854,775,808,
|
||||||
|
// so "m" will be modified to 990,000,000,000,055,555. Then that value
|
||||||
|
// will be rounded to 990,000,000,000,055,555 or
|
||||||
|
// 990,000,000,000,055,556, 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,555,550 or 9,900,000,000,000,555,560.
|
||||||
|
// 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_;
|
||||||
|
}
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
m <= maxRep,
|
||||||
|
"ripple::Number::normalize",
|
||||||
|
"intermediate mantissa fits in int64");
|
||||||
|
mantissa_ = m;
|
||||||
|
|
||||||
auto r = g.round();
|
auto r = g.round();
|
||||||
if (r == 1 || (r == 0 && (mantissa_ & 1) == 1))
|
if (r == 1 || (r == 0 && (mantissa_ & 1) == 1))
|
||||||
@@ -202,11 +300,53 @@ Number::normalize()
|
|||||||
}
|
}
|
||||||
if (exponent_ > maxExponent)
|
if (exponent_ > maxExponent)
|
||||||
throw std::overflow_error("Number::normalize 2");
|
throw std::overflow_error("Number::normalize 2");
|
||||||
|
if (mantissa_ < minMantissa)
|
||||||
|
{
|
||||||
|
// When using the largeRange, the intermediate "m" needs fit within an
|
||||||
|
// int64, but mantissa_ needs to fit within the minMantissa /
|
||||||
|
// maxMantissa range.
|
||||||
|
mantissa_ *= 10;
|
||||||
|
--exponent_;
|
||||||
|
}
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
mantissa_ >= minMantissa && mantissa_ <= maxMantissa,
|
||||||
|
"ripple::Number::normalize",
|
||||||
|
"final mantissa fits in range");
|
||||||
|
|
||||||
if (negative)
|
if (negative)
|
||||||
mantissa_ = -mantissa_;
|
mantissa_ = -mantissa_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Number::normalize()
|
||||||
|
{
|
||||||
|
normalize(
|
||||||
|
mantissa_, exponent_, Number::minMantissa(), Number::maxMantissa());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
|
||||||
|
// the result will be "mostly" normalized, but the exponent could go out of
|
||||||
|
// range.
|
||||||
|
Number
|
||||||
|
Number::shiftExponent(int exponentDelta) const
|
||||||
|
{
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
isnormal(), "ripple::Number::shiftExponent", "normalized");
|
||||||
|
auto const newExponent = exponent_ + exponentDelta;
|
||||||
|
if (newExponent >= maxExponent)
|
||||||
|
throw std::overflow_error("Number::shiftExponent");
|
||||||
|
if (newExponent < minExponent)
|
||||||
|
{
|
||||||
|
return Number{};
|
||||||
|
}
|
||||||
|
Number const result{mantissa_, newExponent, unchecked{}};
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
result.isnormal(),
|
||||||
|
"ripple::Number::shiftExponent",
|
||||||
|
"result is normalized");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Number&
|
Number&
|
||||||
Number::operator+=(Number const& y)
|
Number::operator+=(Number const& y)
|
||||||
{
|
{
|
||||||
@@ -225,16 +365,16 @@ Number::operator+=(Number const& y)
|
|||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
isnormal() && y.isnormal(),
|
isnormal() && y.isnormal(),
|
||||||
"ripple::Number::operator+=(Number) : is normal");
|
"ripple::Number::operator+=(Number) : is normal");
|
||||||
auto xm = mantissa();
|
auto xm = mantissa_;
|
||||||
auto xe = exponent();
|
auto xe = exponent_;
|
||||||
int xn = 1;
|
int xn = 1;
|
||||||
if (xm < 0)
|
if (xm < 0)
|
||||||
{
|
{
|
||||||
xm = -xm;
|
xm = -xm;
|
||||||
xn = -1;
|
xn = -1;
|
||||||
}
|
}
|
||||||
auto ym = y.mantissa();
|
auto ym = y.mantissa_;
|
||||||
auto ye = y.exponent();
|
auto ye = y.exponent_;
|
||||||
int yn = 1;
|
int yn = 1;
|
||||||
if (ym < 0)
|
if (ym < 0)
|
||||||
{
|
{
|
||||||
@@ -266,6 +406,8 @@ Number::operator+=(Number const& y)
|
|||||||
}
|
}
|
||||||
if (xn == yn)
|
if (xn == yn)
|
||||||
{
|
{
|
||||||
|
auto const maxMantissa = Number::maxMantissa();
|
||||||
|
|
||||||
xm += ym;
|
xm += ym;
|
||||||
if (xm > maxMantissa)
|
if (xm > maxMantissa)
|
||||||
{
|
{
|
||||||
@@ -288,6 +430,8 @@ Number::operator+=(Number const& y)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
auto const minMantissa = Number::minMantissa();
|
||||||
|
|
||||||
if (xm > ym)
|
if (xm > ym)
|
||||||
{
|
{
|
||||||
xm = xm - ym;
|
xm = xm - ym;
|
||||||
@@ -322,6 +466,10 @@ Number::operator+=(Number const& y)
|
|||||||
}
|
}
|
||||||
mantissa_ = xm * xn;
|
mantissa_ = xm * xn;
|
||||||
exponent_ = xe;
|
exponent_ = xe;
|
||||||
|
normalize();
|
||||||
|
XRPL_ASSERT(
|
||||||
|
isnormal() || *this == Number{},
|
||||||
|
"ripple::Number::operator+=(Number) : result is normal");
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,16 +514,16 @@ Number::operator*=(Number const& y)
|
|||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
isnormal() && y.isnormal(),
|
isnormal() && y.isnormal(),
|
||||||
"ripple::Number::operator*=(Number) : is normal");
|
"ripple::Number::operator*=(Number) : is normal");
|
||||||
auto xm = mantissa();
|
auto xm = mantissa_;
|
||||||
auto xe = exponent();
|
auto xe = exponent_;
|
||||||
int xn = 1;
|
int xn = 1;
|
||||||
if (xm < 0)
|
if (xm < 0)
|
||||||
{
|
{
|
||||||
xm = -xm;
|
xm = -xm;
|
||||||
xn = -1;
|
xn = -1;
|
||||||
}
|
}
|
||||||
auto ym = y.mantissa();
|
auto ym = y.mantissa_;
|
||||||
auto ye = y.exponent();
|
auto ye = y.exponent_;
|
||||||
int yn = 1;
|
int yn = 1;
|
||||||
if (ym < 0)
|
if (ym < 0)
|
||||||
{
|
{
|
||||||
@@ -388,6 +536,9 @@ Number::operator*=(Number const& y)
|
|||||||
Guard g;
|
Guard g;
|
||||||
if (zn == -1)
|
if (zn == -1)
|
||||||
g.set_negative();
|
g.set_negative();
|
||||||
|
|
||||||
|
auto const maxMantissa = Number::maxMantissa();
|
||||||
|
|
||||||
while (zm > maxMantissa)
|
while (zm > maxMantissa)
|
||||||
{
|
{
|
||||||
// The following is optimization for:
|
// The following is optimization for:
|
||||||
@@ -396,7 +547,7 @@ Number::operator*=(Number const& y)
|
|||||||
g.push(divu10(zm));
|
g.push(divu10(zm));
|
||||||
++ze;
|
++ze;
|
||||||
}
|
}
|
||||||
xm = static_cast<rep>(zm);
|
xm = static_cast<internalrep>(zm);
|
||||||
xe = ze;
|
xe = ze;
|
||||||
auto r = g.round();
|
auto r = g.round();
|
||||||
if (r == 1 || (r == 0 && (xm & 1) == 1))
|
if (r == 1 || (r == 0 && (xm & 1) == 1))
|
||||||
@@ -419,6 +570,7 @@ Number::operator*=(Number const& y)
|
|||||||
std::to_string(xe));
|
std::to_string(xe));
|
||||||
mantissa_ = xm * zn;
|
mantissa_ = xm * zn;
|
||||||
exponent_ = xe;
|
exponent_ = xe;
|
||||||
|
normalize();
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
isnormal() || *this == Number{},
|
isnormal() || *this == Number{},
|
||||||
"ripple::Number::operator*=(Number) : result is normal");
|
"ripple::Number::operator*=(Number) : result is normal");
|
||||||
@@ -433,26 +585,81 @@ Number::operator/=(Number const& y)
|
|||||||
if (*this == Number{})
|
if (*this == Number{})
|
||||||
return *this;
|
return *this;
|
||||||
int np = 1;
|
int np = 1;
|
||||||
auto nm = mantissa();
|
auto nm = mantissa_;
|
||||||
auto ne = exponent();
|
auto ne = exponent_;
|
||||||
if (nm < 0)
|
if (nm < 0)
|
||||||
{
|
{
|
||||||
nm = -nm;
|
nm = -nm;
|
||||||
np = -1;
|
np = -1;
|
||||||
}
|
}
|
||||||
int dp = 1;
|
int dp = 1;
|
||||||
auto dm = y.mantissa();
|
auto dm = y.mantissa_;
|
||||||
auto de = y.exponent();
|
auto de = y.exponent_;
|
||||||
if (dm < 0)
|
if (dm < 0)
|
||||||
{
|
{
|
||||||
dm = -dm;
|
dm = -dm;
|
||||||
dp = -1;
|
dp = -1;
|
||||||
}
|
}
|
||||||
// Shift by 10^17 gives greatest precision while not overflowing uint128_t
|
// Shift by 10^17 gives greatest precision while not overflowing
|
||||||
// or the cast back to int64_t
|
// uint128_t or the cast back to int64_t
|
||||||
uint128_t const f = 100'000'000'000'000'000;
|
// TODO: Can/should this be made bigger for largeRange?
|
||||||
mantissa_ = static_cast<std::int64_t>(uint128_t(nm) * f / uint128_t(dm));
|
// log(2^128,10) ~ 38.5
|
||||||
exponent_ = ne - de - 17;
|
// largeRange.log = 18, fits in 10^19
|
||||||
|
// f can be up to 10^(38-19) = 10^19 safely
|
||||||
|
static_assert(smallRange.log == 15);
|
||||||
|
static_assert(largeRange.log == 18);
|
||||||
|
bool small = Number::getMantissaScale() == MantissaRange::small;
|
||||||
|
uint128_t const f =
|
||||||
|
small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
f >= Number::minMantissa<internalrep>() * 10,
|
||||||
|
"Number::operator/=",
|
||||||
|
"factor expected size");
|
||||||
|
|
||||||
|
// unsigned denominator
|
||||||
|
auto const dmu = static_cast<uint128_t>(dm);
|
||||||
|
// correctionFactor can be anything between 10 and f, depending on how much
|
||||||
|
// extra precision we want to only use for rounding with the
|
||||||
|
// largeMantissa. Three digits seems like plenty, and is more than
|
||||||
|
// the smallMantissa uses.
|
||||||
|
uint128_t const correctionFactor = 1'000;
|
||||||
|
|
||||||
|
auto const numerator = uint128_t(nm) * f;
|
||||||
|
|
||||||
|
mantissa_ = numerator / dmu;
|
||||||
|
exponent_ = ne - de - (small ? 17 : 19);
|
||||||
|
if (!small)
|
||||||
|
{
|
||||||
|
// Virtually multiply numerator by correctionFactor. Since that would
|
||||||
|
// overflow in the existing uint128_t, we'll do that part separately.
|
||||||
|
// The math for this would work for small mantissas, but we need to
|
||||||
|
// preserve existing behavior.
|
||||||
|
//
|
||||||
|
// Consider:
|
||||||
|
// ((numerator * correctionFactor) / dmu) / correctionFactor
|
||||||
|
// = ((numerator / dmu) * correctionFactor) / correctionFactor)
|
||||||
|
//
|
||||||
|
// But that assumes infinite precision. With integer math, this is
|
||||||
|
// equivalent to
|
||||||
|
//
|
||||||
|
// = ((numerator / dmu * correctionFactor)
|
||||||
|
// + ((numerator % dmu) * correctionFactor) / dmu) / correctionFactor
|
||||||
|
//
|
||||||
|
// We have already set `mantissa_ = numerator / dmu`. Now we
|
||||||
|
// compute `remainder = numerator % dmu`, and if it is
|
||||||
|
// nonzero, we do the rest of the arithmetic. If it's zero, we can skip
|
||||||
|
// it.
|
||||||
|
auto const remainder = (numerator % dmu);
|
||||||
|
if (remainder != 0)
|
||||||
|
{
|
||||||
|
mantissa_ *= correctionFactor;
|
||||||
|
auto const correction = remainder * correctionFactor / dmu;
|
||||||
|
mantissa_ += correction;
|
||||||
|
// divide by 1000 by moving the exponent, so we don't lose the
|
||||||
|
// integer value we just computed
|
||||||
|
exponent_ -= 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
mantissa_ *= np * dp;
|
mantissa_ *= np * dp;
|
||||||
normalize();
|
normalize();
|
||||||
return *this;
|
return *this;
|
||||||
@@ -460,7 +667,7 @@ Number::operator/=(Number const& y)
|
|||||||
|
|
||||||
Number::operator rep() const
|
Number::operator rep() const
|
||||||
{
|
{
|
||||||
rep drops = mantissa_;
|
internalrep drops = mantissa_;
|
||||||
int offset = exponent_;
|
int offset = exponent_;
|
||||||
Guard g;
|
Guard g;
|
||||||
if (drops != 0)
|
if (drops != 0)
|
||||||
@@ -475,9 +682,16 @@ Number::operator rep() const
|
|||||||
g.push(drops % 10);
|
g.push(drops % 10);
|
||||||
drops /= 10;
|
drops /= 10;
|
||||||
}
|
}
|
||||||
|
if (offset == 0 && drops > std::numeric_limits<rep>::max())
|
||||||
|
{
|
||||||
|
// If offset == 0, then the loop won't run, and the overflow check
|
||||||
|
// won't be made, but a int128 can overflow int64 by itself, so
|
||||||
|
// check here.
|
||||||
|
throw std::overflow_error("Number::operator rep() overflow");
|
||||||
|
}
|
||||||
for (; offset > 0; --offset)
|
for (; offset > 0; --offset)
|
||||||
{
|
{
|
||||||
if (drops > std::numeric_limits<decltype(drops)>::max() / 10)
|
if (drops > std::numeric_limits<rep>::max() / 10)
|
||||||
throw std::overflow_error("Number::operator rep() overflow");
|
throw std::overflow_error("Number::operator rep() overflow");
|
||||||
drops *= 10;
|
drops *= 10;
|
||||||
}
|
}
|
||||||
@@ -489,7 +703,7 @@ Number::operator rep() const
|
|||||||
if (g.is_negative())
|
if (g.is_negative())
|
||||||
drops = -drops;
|
drops = -drops;
|
||||||
}
|
}
|
||||||
return drops;
|
return static_cast<rep>(drops);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
@@ -499,31 +713,42 @@ to_string(Number const& amount)
|
|||||||
if (amount == Number{})
|
if (amount == Number{})
|
||||||
return "0";
|
return "0";
|
||||||
|
|
||||||
auto const exponent = amount.exponent();
|
auto const exponent = amount.exponent_;
|
||||||
auto mantissa = amount.mantissa();
|
|
||||||
|
bool const negative = amount.mantissa_ < 0;
|
||||||
|
|
||||||
|
auto const mantissa = [&]() {
|
||||||
|
auto mantissa = amount.mantissa_;
|
||||||
|
if (negative)
|
||||||
|
{
|
||||||
|
mantissa = -mantissa;
|
||||||
|
}
|
||||||
|
XRPL_ASSERT(
|
||||||
|
mantissa < std::numeric_limits<std::uint64_t>::max(),
|
||||||
|
"ripple::to_string(Number) : mantissa fits in uin64_t");
|
||||||
|
return static_cast<std::uint64_t>(mantissa);
|
||||||
|
}();
|
||||||
|
|
||||||
// Use scientific notation for exponents that are too small or too large
|
// Use scientific notation for exponents that are too small or too large
|
||||||
if (((exponent != 0) && ((exponent < -25) || (exponent > -5))))
|
auto const rangeLog = Number::mantissaLog();
|
||||||
|
if (((exponent != 0) &&
|
||||||
|
((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
|
||||||
{
|
{
|
||||||
std::string ret = std::to_string(mantissa);
|
std::string ret = negative ? "-" : "";
|
||||||
|
ret.append(std::to_string(mantissa));
|
||||||
ret.append(1, 'e');
|
ret.append(1, 'e');
|
||||||
ret.append(std::to_string(exponent));
|
ret.append(std::to_string(exponent));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool negative = false;
|
// TODO: These numbers are probably wrong for largeRange
|
||||||
|
|
||||||
if (mantissa < 0)
|
|
||||||
{
|
|
||||||
mantissa = -mantissa;
|
|
||||||
negative = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
exponent + 43 > 0, "ripple::to_string(Number) : minimum exponent");
|
exponent + 43 > 0, "ripple::to_string(Number) : minimum exponent");
|
||||||
|
|
||||||
ptrdiff_t const pad_prefix = 27;
|
auto const mantissaLog = Number::mantissaLog();
|
||||||
ptrdiff_t const pad_suffix = 23;
|
|
||||||
|
ptrdiff_t const pad_prefix = mantissaLog + 12;
|
||||||
|
ptrdiff_t const pad_suffix = mantissaLog + 8;
|
||||||
|
|
||||||
std::string const raw_value(std::to_string(mantissa));
|
std::string const raw_value(std::to_string(mantissa));
|
||||||
std::string val;
|
std::string val;
|
||||||
@@ -533,7 +758,7 @@ to_string(Number const& amount)
|
|||||||
val.append(raw_value);
|
val.append(raw_value);
|
||||||
val.append(pad_suffix, '0');
|
val.append(pad_suffix, '0');
|
||||||
|
|
||||||
ptrdiff_t const offset(exponent + 43);
|
ptrdiff_t const offset(exponent + pad_prefix + mantissaLog + 1);
|
||||||
|
|
||||||
auto pre_from(val.begin());
|
auto pre_from(val.begin());
|
||||||
auto const pre_to(val.begin() + offset);
|
auto const pre_to(val.begin() + offset);
|
||||||
@@ -594,7 +819,7 @@ Number
|
|||||||
power(Number const& f, unsigned n)
|
power(Number const& f, unsigned n)
|
||||||
{
|
{
|
||||||
if (n == 0)
|
if (n == 0)
|
||||||
return one;
|
return Number::one();
|
||||||
if (n == 1)
|
if (n == 1)
|
||||||
return f;
|
return f;
|
||||||
auto r = power(f, n / 2);
|
auto r = power(f, n / 2);
|
||||||
@@ -616,6 +841,8 @@ power(Number const& f, unsigned n)
|
|||||||
Number
|
Number
|
||||||
root(Number f, unsigned d)
|
root(Number f, unsigned d)
|
||||||
{
|
{
|
||||||
|
auto const one = Number::one();
|
||||||
|
|
||||||
if (f == one || d == 1)
|
if (f == one || d == 1)
|
||||||
return f;
|
return f;
|
||||||
if (d == 0)
|
if (d == 0)
|
||||||
@@ -632,7 +859,7 @@ root(Number f, unsigned d)
|
|||||||
return f;
|
return f;
|
||||||
|
|
||||||
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
|
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
|
||||||
auto e = f.exponent() + 16;
|
auto e = f.exponent() + Number::mantissaLog() + 1;
|
||||||
auto const di = static_cast<int>(d);
|
auto const di = static_cast<int>(d);
|
||||||
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
|
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
|
||||||
{
|
{
|
||||||
@@ -643,7 +870,9 @@ root(Number f, unsigned d)
|
|||||||
return di - k2;
|
return di - k2;
|
||||||
}();
|
}();
|
||||||
e += ex;
|
e += ex;
|
||||||
f = Number{f.mantissa(), f.exponent() - e}; // f /= 10^e;
|
f = f.shiftExponent(-e); // f /= 10^e;
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
f.isnormal(), "ripple::root(Number, unsigned)", "f is normalized");
|
||||||
bool neg = false;
|
bool neg = false;
|
||||||
if (f < Number{})
|
if (f < Number{})
|
||||||
{
|
{
|
||||||
@@ -675,12 +904,19 @@ root(Number f, unsigned d)
|
|||||||
} while (r != rm1 && r != rm2);
|
} while (r != rm1 && r != rm2);
|
||||||
|
|
||||||
// return r * 10^(e/d) to reverse scaling
|
// return r * 10^(e/d) to reverse scaling
|
||||||
return Number{r.mantissa(), r.exponent() + e / di};
|
auto const result = r.shiftExponent(e / di);
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
result.isnormal(),
|
||||||
|
"ripple::root(Number, unsigned)",
|
||||||
|
"result is normalized");
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Number
|
Number
|
||||||
root2(Number f)
|
root2(Number f)
|
||||||
{
|
{
|
||||||
|
auto const one = Number::one();
|
||||||
|
|
||||||
if (f == one)
|
if (f == one)
|
||||||
return f;
|
return f;
|
||||||
if (f < Number{})
|
if (f < Number{})
|
||||||
@@ -689,10 +925,11 @@ root2(Number f)
|
|||||||
return f;
|
return f;
|
||||||
|
|
||||||
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
|
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
|
||||||
auto e = f.exponent() + 16;
|
auto e = f.exponent() + Number::mantissaLog() + 1;
|
||||||
if (e % 2 != 0)
|
if (e % 2 != 0)
|
||||||
++e;
|
++e;
|
||||||
f = Number{f.mantissa(), f.exponent() - e}; // f /= 10^e;
|
f = f.shiftExponent(-e); // f /= 10^e;
|
||||||
|
XRPL_ASSERT_PARTS(f.isnormal(), "ripple::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;
|
||||||
@@ -713,7 +950,11 @@ root2(Number f)
|
|||||||
} while (r != rm1 && r != rm2);
|
} while (r != rm1 && r != rm2);
|
||||||
|
|
||||||
// return r * 10^(e/2) to reverse scaling
|
// return r * 10^(e/2) to reverse scaling
|
||||||
return Number{r.mantissa(), r.exponent() + e / 2};
|
auto const result = r.shiftExponent(e / 2);
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
result.isnormal(), "ripple::root2(Number)", "result is normalized");
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns f^(n/d)
|
// Returns f^(n/d)
|
||||||
@@ -721,6 +962,8 @@ root2(Number f)
|
|||||||
Number
|
Number
|
||||||
power(Number const& f, unsigned n, unsigned d)
|
power(Number const& f, unsigned n, unsigned d)
|
||||||
{
|
{
|
||||||
|
auto const one = Number::one();
|
||||||
|
|
||||||
if (f == one)
|
if (f == one)
|
||||||
return f;
|
return f;
|
||||||
auto g = std::gcd(n, d);
|
auto g = std::gcd(n, d);
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
#include <xrpl/protocol/IOUAmount.h>
|
||||||
|
// Do not remove. Forces IOUAmount.h to stay first, to verify it can compile
|
||||||
|
// without any hidden dependencies
|
||||||
#include <xrpl/basics/LocalValue.h>
|
#include <xrpl/basics/LocalValue.h>
|
||||||
#include <xrpl/basics/Number.h>
|
#include <xrpl/basics/Number.h>
|
||||||
#include <xrpl/basics/contract.h>
|
#include <xrpl/basics/contract.h>
|
||||||
#include <xrpl/beast/utility/Zero.h>
|
#include <xrpl/beast/utility/Zero.h>
|
||||||
#include <xrpl/protocol/IOUAmount.h>
|
#include <xrpl/protocol/STAmount.h>
|
||||||
|
|
||||||
#include <boost/multiprecision/cpp_int.hpp>
|
#include <boost/multiprecision/cpp_int.hpp>
|
||||||
|
|
||||||
@@ -40,11 +43,19 @@ setSTNumberSwitchover(bool v)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* The range for the mantissa when normalized */
|
/* The range for the mantissa when normalized */
|
||||||
static std::int64_t constexpr minMantissa = 1000000000000000ull;
|
// log(2^63,10) ~ 18.96
|
||||||
static std::int64_t constexpr maxMantissa = 9999999999999999ull;
|
//
|
||||||
|
static std::int64_t constexpr minMantissa = STAmount::cMinValue;
|
||||||
|
static std::int64_t constexpr maxMantissa = STAmount::cMaxValue;
|
||||||
/* The range for the exponent when normalized */
|
/* The range for the exponent when normalized */
|
||||||
static int constexpr minExponent = -96;
|
static int constexpr minExponent = STAmount::cMinOffset;
|
||||||
static int constexpr maxExponent = 80;
|
static int constexpr maxExponent = STAmount::cMaxOffset;
|
||||||
|
|
||||||
|
std::pair<IOUAmount::mantissa_type, IOUAmount::exponent_type>
|
||||||
|
IOUAmount::scaleNumber(Number const& number)
|
||||||
|
{
|
||||||
|
return number.normalizeToRange(minMantissa, maxMantissa);
|
||||||
|
}
|
||||||
|
|
||||||
IOUAmount
|
IOUAmount
|
||||||
IOUAmount::minPositiveAmount()
|
IOUAmount::minPositiveAmount()
|
||||||
@@ -64,8 +75,7 @@ IOUAmount::normalize()
|
|||||||
if (getSTNumberSwitchover())
|
if (getSTNumberSwitchover())
|
||||||
{
|
{
|
||||||
Number const v{mantissa_, exponent_};
|
Number const v{mantissa_, exponent_};
|
||||||
mantissa_ = v.mantissa();
|
std::tie(mantissa_, exponent_) = scaleNumber(v);
|
||||||
exponent_ = v.exponent();
|
|
||||||
if (exponent_ > maxExponent)
|
if (exponent_ > maxExponent)
|
||||||
Throw<std::overflow_error>("value overflow");
|
Throw<std::overflow_error>("value overflow");
|
||||||
if (exponent_ < minExponent)
|
if (exponent_ < minExponent)
|
||||||
@@ -106,8 +116,7 @@ IOUAmount::normalize()
|
|||||||
mantissa_ = -mantissa_;
|
mantissa_ = -mantissa_;
|
||||||
}
|
}
|
||||||
|
|
||||||
IOUAmount::IOUAmount(Number const& other)
|
IOUAmount::IOUAmount(Number const& other) : IOUAmount(scaleNumber(other))
|
||||||
: mantissa_(other.mantissa()), exponent_(other.exponent())
|
|
||||||
{
|
{
|
||||||
if (exponent_ > maxExponent)
|
if (exponent_ > maxExponent)
|
||||||
Throw<std::overflow_error>("value overflow");
|
Throw<std::overflow_error>("value overflow");
|
||||||
|
|||||||
@@ -49,6 +49,12 @@ Issue::native() const
|
|||||||
return *this == xrpIssue();
|
return *this == xrpIssue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Issue::integral() const
|
||||||
|
{
|
||||||
|
return native();
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
isConsistent(Issue const& ac)
|
isConsistent(Issue const& ac)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
#include <xrpl/protocol/Rules.h>
|
||||||
|
// Do not remove. Forces Rules.h to stay first, to verify it can compile
|
||||||
|
// without any hidden dependencies
|
||||||
#include <xrpl/basics/LocalValue.h>
|
#include <xrpl/basics/LocalValue.h>
|
||||||
|
#include <xrpl/basics/Number.h>
|
||||||
#include <xrpl/basics/base_uint.h>
|
#include <xrpl/basics/base_uint.h>
|
||||||
#include <xrpl/basics/hardened_hash.h>
|
#include <xrpl/basics/hardened_hash.h>
|
||||||
#include <xrpl/beast/hash/uhash.h>
|
#include <xrpl/beast/hash/uhash.h>
|
||||||
#include <xrpl/beast/utility/instrumentation.h>
|
#include <xrpl/beast/utility/instrumentation.h>
|
||||||
#include <xrpl/protocol/Feature.h>
|
#include <xrpl/protocol/Feature.h>
|
||||||
#include <xrpl/protocol/Rules.h>
|
|
||||||
#include <xrpl/protocol/STVector256.h>
|
#include <xrpl/protocol/STVector256.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -33,6 +36,15 @@ getCurrentTransactionRules()
|
|||||||
void
|
void
|
||||||
setCurrentTransactionRules(std::optional<Rules> r)
|
setCurrentTransactionRules(std::optional<Rules> r)
|
||||||
{
|
{
|
||||||
|
// Make global changes associated with the rules before the value is moved.
|
||||||
|
// Push the appropriate setting, instead of having the class pull every time
|
||||||
|
// the value is needed. That could get expensive fast.
|
||||||
|
bool enableLargeNumbers = !r ||
|
||||||
|
(r->enabled(featureSingleAssetVault) /*||
|
||||||
|
r->enabled(featureLendingProtocol)*/);
|
||||||
|
Number::setMantissaScale(
|
||||||
|
enableLargeNumbers ? MantissaRange::large : MantissaRange::small);
|
||||||
|
|
||||||
*getCurrentTransactionRulesRef() = std::move(r);
|
*getCurrentTransactionRulesRef() = std::move(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -310,8 +310,8 @@ STAmount&
|
|||||||
STAmount::operator=(IOUAmount const& iou)
|
STAmount::operator=(IOUAmount const& iou)
|
||||||
{
|
{
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
native() == false,
|
integral() == false,
|
||||||
"ripple::STAmount::operator=(IOUAmount) : is not XRP");
|
"ripple::STAmount::operator=(IOUAmount) : is not integral");
|
||||||
mOffset = iou.exponent();
|
mOffset = iou.exponent();
|
||||||
mIsNegative = iou < beast::zero;
|
mIsNegative = iou < beast::zero;
|
||||||
if (mIsNegative)
|
if (mIsNegative)
|
||||||
@@ -851,8 +851,9 @@ STAmount::canonicalize()
|
|||||||
|
|
||||||
if (getSTNumberSwitchover())
|
if (getSTNumberSwitchover())
|
||||||
{
|
{
|
||||||
|
auto const value = unsafe_cast<std::int64_t>(mValue);
|
||||||
Number num(
|
Number num(
|
||||||
mIsNegative ? -mValue : mValue, mOffset, Number::unchecked{});
|
mIsNegative ? -value : value, mOffset, Number::unchecked{});
|
||||||
auto set = [&](auto const& val) {
|
auto set = [&](auto const& val) {
|
||||||
mIsNegative = val.value() < 0;
|
mIsNegative = val.value() < 0;
|
||||||
mValue = mIsNegative ? -val.value() : val.value();
|
mValue = mIsNegative ? -val.value() : val.value();
|
||||||
@@ -1323,7 +1324,7 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset)
|
|||||||
if (getSTNumberSwitchover())
|
if (getSTNumberSwitchover())
|
||||||
{
|
{
|
||||||
auto const r = Number{v1} * Number{v2};
|
auto const r = Number{v1} * Number{v2};
|
||||||
return STAmount{asset, r.mantissa(), r.exponent()};
|
return STAmount{asset, r};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uint64_t value1 = v1.mantissa();
|
std::uint64_t value1 = v1.mantissa();
|
||||||
|
|||||||
@@ -50,8 +50,16 @@ STNumber::add(Serializer& s) const
|
|||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
getFName().fieldType == getSType(),
|
getFName().fieldType == getSType(),
|
||||||
"ripple::STNumber::add : field type match");
|
"ripple::STNumber::add : field type match");
|
||||||
s.add64(value_.mantissa());
|
|
||||||
s.add32(value_.exponent());
|
auto const mantissa = value_.mantissa();
|
||||||
|
auto const exponent = value_.exponent();
|
||||||
|
XRPL_ASSERT_PARTS(
|
||||||
|
mantissa <= std::numeric_limits<std::int64_t>::max() &&
|
||||||
|
mantissa >= std::numeric_limits<std::int64_t>::min(),
|
||||||
|
"ripple::STNumber::add",
|
||||||
|
"mantissa in valid range");
|
||||||
|
s.add64(mantissa);
|
||||||
|
s.add32(exponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
Number const&
|
Number const&
|
||||||
@@ -180,20 +188,22 @@ numberFromJson(SField const& field, Json::Value const& value)
|
|||||||
else if (value.isString())
|
else if (value.isString())
|
||||||
{
|
{
|
||||||
parts = partsFromString(value.asString());
|
parts = partsFromString(value.asString());
|
||||||
// Only strings can represent out-of-range values.
|
// Number mantissas are much bigger than the allowable parsed values, so
|
||||||
if (parts.mantissa > std::numeric_limits<std::int64_t>::max())
|
// it can't be out of range.
|
||||||
Throw<std::range_error>("too high");
|
static_assert(
|
||||||
|
std::numeric_limits<numberint>::max() >=
|
||||||
|
std::numeric_limits<decltype(parts.mantissa)>::max());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Throw<std::runtime_error>("not a number");
|
Throw<std::runtime_error>("not a number");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::int64_t mantissa = parts.mantissa;
|
numberint mantissa = parts.mantissa;
|
||||||
if (parts.negative)
|
if (parts.negative)
|
||||||
mantissa = -mantissa;
|
mantissa = -mantissa;
|
||||||
|
|
||||||
return STNumber{field, Number{mantissa, parts.exponent}};
|
return STNumber{field, Number{mantissa, parts.exponent, Number::normalized{}}};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ namespace test {
|
|||||||
*/
|
*/
|
||||||
struct AMM_test : public jtx::AMMTest
|
struct AMM_test : public jtx::AMMTest
|
||||||
{
|
{
|
||||||
|
// Use small Number mantissas for the life of this test.
|
||||||
|
NumberMantissaScaleGuard sg_{ripple::MantissaRange::small};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void
|
void
|
||||||
testInstanceCreate()
|
testInstanceCreate()
|
||||||
@@ -1384,15 +1387,14 @@ private:
|
|||||||
// equal asset deposit: unit test to exercise the rounding-down of
|
// equal asset deposit: unit test to exercise the rounding-down of
|
||||||
// LPTokens in the AMMHelpers.cpp: adjustLPTokens calculations
|
// LPTokens in the AMMHelpers.cpp: adjustLPTokens calculations
|
||||||
// The LPTokens need to have 16 significant digits and a fractional part
|
// The LPTokens need to have 16 significant digits and a fractional part
|
||||||
for (Number const deltaLPTokens :
|
for (Number const& deltaLPTokens :
|
||||||
{Number{UINT64_C(100000'0000000009), -10},
|
{Number{UINT64_C(100000'0000000009), -10},
|
||||||
Number{UINT64_C(100000'0000000001), -10}})
|
Number{UINT64_C(100000'0000000001), -10}})
|
||||||
{
|
{
|
||||||
testAMM([&](AMM& ammAlice, Env& env) {
|
testAMM([&](AMM& ammAlice, Env& env) {
|
||||||
// initial LPToken balance
|
// initial LPToken balance
|
||||||
IOUAmount const initLPToken = ammAlice.getLPTokensBalance();
|
IOUAmount const initLPToken = ammAlice.getLPTokensBalance();
|
||||||
IOUAmount const newLPTokens{
|
IOUAmount const newLPTokens{deltaLPTokens};
|
||||||
deltaLPTokens.mantissa(), deltaLPTokens.exponent()};
|
|
||||||
|
|
||||||
// carol performs a two-asset deposit
|
// carol performs a two-asset deposit
|
||||||
ammAlice.deposit(
|
ammAlice.deposit(
|
||||||
@@ -1417,11 +1419,9 @@ private:
|
|||||||
Number const deltaXRP = fr * 1e10;
|
Number const deltaXRP = fr * 1e10;
|
||||||
Number const deltaUSD = fr * 1e4;
|
Number const deltaUSD = fr * 1e4;
|
||||||
|
|
||||||
STAmount const depositUSD =
|
STAmount const depositUSD = STAmount{USD, deltaUSD};
|
||||||
STAmount{USD, deltaUSD.mantissa(), deltaUSD.exponent()};
|
|
||||||
|
|
||||||
STAmount const depositXRP =
|
STAmount const depositXRP = STAmount{XRP, deltaXRP};
|
||||||
STAmount{XRP, deltaXRP.mantissa(), deltaXRP.exponent()};
|
|
||||||
|
|
||||||
// initial LPTokens (1e7) + newLPTokens
|
// initial LPTokens (1e7) + newLPTokens
|
||||||
BEAST_EXPECT(ammAlice.expectBalances(
|
BEAST_EXPECT(ammAlice.expectBalances(
|
||||||
@@ -3014,6 +3014,11 @@ private:
|
|||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
// For now, just disable SAV entirely, which locks in the small Number
|
||||||
|
// mantissas
|
||||||
|
features =
|
||||||
|
features - featureSingleAssetVault /* - featureLendingProtocol */;
|
||||||
|
|
||||||
// Auction slot initially is owned by AMM creator, who pays 0 price.
|
// Auction slot initially is owned by AMM creator, who pays 0 price.
|
||||||
|
|
||||||
// Bid 110 tokens. Pay bidMin.
|
// Bid 110 tokens. Pay bidMin.
|
||||||
@@ -3758,6 +3763,11 @@ private:
|
|||||||
testcase("Basic Payment");
|
testcase("Basic Payment");
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
|
|
||||||
|
// For now, just disable SAV entirely, which locks in the small Number
|
||||||
|
// mantissas
|
||||||
|
features =
|
||||||
|
features - featureSingleAssetVault /* - featureLendingProtocol */;
|
||||||
|
|
||||||
// Payment 100USD for 100XRP.
|
// Payment 100USD for 100XRP.
|
||||||
// Force one path with tfNoRippleDirect.
|
// Force one path with tfNoRippleDirect.
|
||||||
testAMM(
|
testAMM(
|
||||||
@@ -6476,6 +6486,8 @@ private:
|
|||||||
Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
|
Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
|
||||||
auto rules = env.current()->rules();
|
auto rules = env.current()->rules();
|
||||||
CurrentTransactionRulesGuard rg(rules);
|
CurrentTransactionRulesGuard rg(rules);
|
||||||
|
NumberMantissaScaleGuard sg(MantissaRange::small);
|
||||||
|
|
||||||
for (auto const& t : tests)
|
for (auto const& t : tests)
|
||||||
{
|
{
|
||||||
auto getPool = [&](std::string const& v, bool isXRP) {
|
auto getPool = [&](std::string const& v, bool isXRP) {
|
||||||
|
|||||||
@@ -2474,7 +2474,7 @@ class MPToken_test : public beast::unit_test::suite
|
|||||||
alice.name(), makeMptID(env.seq(alice), alice));
|
alice.name(), makeMptID(env.seq(alice), alice));
|
||||||
|
|
||||||
Json::Value jv = claw(alice, mpt(1), bob);
|
Json::Value jv = claw(alice, mpt(1), bob);
|
||||||
jv[jss::Amount][jss::value] = to_string(maxMPTokenAmount + 1);
|
jv[jss::Amount][jss::value] = std::to_string(maxMPTokenAmount + 1);
|
||||||
Json::Value jv1;
|
Json::Value jv1;
|
||||||
jv1[jss::secret] = alice.name();
|
jv1[jss::secret] = alice.name();
|
||||||
jv1[jss::tx_json] = jv;
|
jv1[jss::tx_json] = jv;
|
||||||
|
|||||||
@@ -4525,7 +4525,7 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
BEAST_EXPECT(checkString(vault, sfAssetsAvailable, "50"));
|
BEAST_EXPECT(checkString(vault, sfAssetsAvailable, "50"));
|
||||||
BEAST_EXPECT(checkString(vault, sfAssetsMaximum, "1000"));
|
BEAST_EXPECT(checkString(vault, sfAssetsMaximum, "1000"));
|
||||||
BEAST_EXPECT(checkString(vault, sfAssetsTotal, "50"));
|
BEAST_EXPECT(checkString(vault, sfAssetsTotal, "50"));
|
||||||
BEAST_EXPECT(checkString(vault, sfLossUnrealized, "0"));
|
BEAST_EXPECT(!vault.isMember(sfLossUnrealized.getJsonName()));
|
||||||
|
|
||||||
auto const strShareID = strHex(sle->at(sfShareMPTID));
|
auto const strShareID = strHex(sle->at(sfShareMPTID));
|
||||||
BEAST_EXPECT(checkString(vault, sfShareMPTID, strShareID));
|
BEAST_EXPECT(checkString(vault, sfShareMPTID, strShareID));
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -192,6 +192,12 @@ public:
|
|||||||
return to_json(asset_);
|
return to_json(asset_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
integral() const
|
||||||
|
{
|
||||||
|
return asset_.integral();
|
||||||
|
}
|
||||||
|
|
||||||
template <std::integral T>
|
template <std::integral T>
|
||||||
PrettyAmount
|
PrettyAmount
|
||||||
operator()(T v, Number::rounding_mode rounding = Number::getround()) const
|
operator()(T v, Number::rounding_mode rounding = Number::getround()) const
|
||||||
@@ -242,6 +248,12 @@ struct XRP_t
|
|||||||
return xrpIssue();
|
return xrpIssue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
integral() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns an amount of XRP as PrettyAmount,
|
/** Returns an amount of XRP as PrettyAmount,
|
||||||
which is trivially convertible to STAmount
|
which is trivially convertible to STAmount
|
||||||
|
|
||||||
@@ -366,6 +378,11 @@ public:
|
|||||||
{
|
{
|
||||||
return issue();
|
return issue();
|
||||||
}
|
}
|
||||||
|
bool
|
||||||
|
integral() const
|
||||||
|
{
|
||||||
|
return issue().integral();
|
||||||
|
}
|
||||||
|
|
||||||
/** Implicit conversion to Issue or Asset.
|
/** Implicit conversion to Issue or Asset.
|
||||||
|
|
||||||
@@ -456,6 +473,11 @@ public:
|
|||||||
{
|
{
|
||||||
return mptIssue();
|
return mptIssue();
|
||||||
}
|
}
|
||||||
|
bool
|
||||||
|
integral() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/** Implicit conversion to MPTIssue or asset.
|
/** Implicit conversion to MPTIssue or asset.
|
||||||
|
|
||||||
|
|||||||
@@ -29,10 +29,8 @@ struct STNumber_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
run() override
|
doRun()
|
||||||
{
|
{
|
||||||
static_assert(!std::is_convertible_v<STNumber*, Number*>);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
STNumber const stnum{sfNumber};
|
STNumber const stnum{sfNumber};
|
||||||
BEAST_EXPECT(stnum.getSType() == STI_NUMBER);
|
BEAST_EXPECT(stnum.getSType() == STI_NUMBER);
|
||||||
@@ -127,6 +125,40 @@ struct STNumber_test : public beast::unit_test::suite
|
|||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
numberFromJson(sfNumber, "-0.000e6") == STNumber(sfNumber, 0));
|
numberFromJson(sfNumber, "-0.000e6") == STNumber(sfNumber, 0));
|
||||||
|
|
||||||
|
{
|
||||||
|
NumberRoundModeGuard mg(Number::towards_zero);
|
||||||
|
// maxint64 9,223,372,036,854,775,807
|
||||||
|
auto const maxInt =
|
||||||
|
std::to_string(std::numeric_limits<std::int64_t>::max());
|
||||||
|
// minint64 -9,223,372,036,854,775,808
|
||||||
|
auto const minInt =
|
||||||
|
std::to_string(std::numeric_limits<std::int64_t>::min());
|
||||||
|
if (Number::getMantissaScale() == MantissaRange::small)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(
|
||||||
|
numberFromJson(sfNumber, maxInt) ==
|
||||||
|
STNumber(sfNumber, Number{9'223'372'036'854'775, 3}));
|
||||||
|
BEAST_EXPECT(
|
||||||
|
numberFromJson(sfNumber, minInt) ==
|
||||||
|
STNumber(sfNumber, Number{-9'223'372'036'854'775, 3}));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(
|
||||||
|
numberFromJson(sfNumber, maxInt) ==
|
||||||
|
STNumber(
|
||||||
|
sfNumber, Number{9'223'372'036'854'775'807, 0}));
|
||||||
|
BEAST_EXPECT(
|
||||||
|
numberFromJson(sfNumber, minInt) ==
|
||||||
|
STNumber(
|
||||||
|
sfNumber,
|
||||||
|
-Number{
|
||||||
|
numberint(9'223'372'036'854'775) * 1000 + 808,
|
||||||
|
0,
|
||||||
|
Number::normalized{}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto imin = std::numeric_limits<int>::min();
|
constexpr auto imin = std::numeric_limits<int>::min();
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
numberFromJson(sfNumber, imin) ==
|
numberFromJson(sfNumber, imin) ==
|
||||||
@@ -279,15 +311,21 @@ struct STNumber_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
run() override
|
||||||
|
{
|
||||||
|
static_assert(!std::is_convertible_v<STNumber*, Number*>);
|
||||||
|
|
||||||
|
for (auto const scale : {MantissaRange::small, MantissaRange::large})
|
||||||
|
{
|
||||||
|
NumberMantissaScaleGuard sg(scale);
|
||||||
|
testcase << to_string(Number::getMantissaScale());
|
||||||
|
doRun();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BEAST_DEFINE_TESTSUITE(STNumber, protocol, ripple);
|
BEAST_DEFINE_TESTSUITE(STNumber, protocol, ripple);
|
||||||
|
|
||||||
void
|
|
||||||
testCompile(std::ostream& out)
|
|
||||||
{
|
|
||||||
STNumber number{sfNumber, 42};
|
|
||||||
out << number;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|||||||
@@ -592,6 +592,24 @@ Transactor::ticketDelete(
|
|||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Transactor::useOldNumberRules(TxType txType)
|
||||||
|
{
|
||||||
|
constexpr auto skipTransactions = std::to_array<TxType>(
|
||||||
|
{ttAMM_BID,
|
||||||
|
ttAMM_CLAWBACK,
|
||||||
|
ttAMM_CREATE,
|
||||||
|
ttAMM_DELETE,
|
||||||
|
ttAMM_DEPOSIT,
|
||||||
|
ttAMM_VOTE,
|
||||||
|
ttAMM_WITHDRAW});
|
||||||
|
|
||||||
|
return std::find(
|
||||||
|
std::begin(skipTransactions),
|
||||||
|
std::end(skipTransactions),
|
||||||
|
txType) != std::end(skipTransactions);
|
||||||
|
}
|
||||||
|
|
||||||
// check stuff before you bother to lock the ledger
|
// check stuff before you bother to lock the ledger
|
||||||
void
|
void
|
||||||
Transactor::preCompute()
|
Transactor::preCompute()
|
||||||
@@ -1106,10 +1124,16 @@ Transactor::operator()()
|
|||||||
{
|
{
|
||||||
JLOG(j_.trace()) << "apply: " << ctx_.tx.getTransactionID();
|
JLOG(j_.trace()) << "apply: " << ctx_.tx.getTransactionID();
|
||||||
|
|
||||||
|
// These global updates really should have been for every Transaction
|
||||||
|
// step: preflight, preclaim, and doApply. And even calculateBaseFee. See
|
||||||
|
// with_txn_type().
|
||||||
|
//
|
||||||
// raii classes for the current ledger rules.
|
// raii classes for the current ledger rules.
|
||||||
// fixUniversalNumber predate the rulesGuard and should be replaced.
|
// fixUniversalNumber predate the rulesGuard and should be replaced.
|
||||||
NumberSO stNumberSO{view().rules().enabled(fixUniversalNumber)};
|
NumberSO stNumberSO{view().rules().enabled(fixUniversalNumber)};
|
||||||
CurrentTransactionRulesGuard currentTransctionRulesGuard(view().rules());
|
CurrentTransactionRulesGuard currentTransctionRulesGuard(view().rules());
|
||||||
|
if (Transactor::useOldNumberRules(ctx_.tx.getTxnType()))
|
||||||
|
Number::setMantissaScale(MantissaRange::small);
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
{
|
{
|
||||||
@@ -1122,7 +1146,7 @@ Transactor::operator()()
|
|||||||
{
|
{
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
JLOG(j_.fatal()) << "Transaction serdes mismatch";
|
JLOG(j_.fatal()) << "Transaction serdes mismatch";
|
||||||
JLOG(j_.info()) << to_string(ctx_.tx.getJson(JsonOptions::none));
|
JLOG(j_.fatal()) << ctx_.tx.getJson(JsonOptions::none);
|
||||||
JLOG(j_.fatal()) << s2.getJson(JsonOptions::none);
|
JLOG(j_.fatal()) << s2.getJson(JsonOptions::none);
|
||||||
UNREACHABLE(
|
UNREACHABLE(
|
||||||
"ripple::Transactor::operator() : transaction serdes mismatch");
|
"ripple::Transactor::operator() : transaction serdes mismatch");
|
||||||
|
|||||||
@@ -230,6 +230,9 @@ public:
|
|||||||
uint256 const& ticketIndex,
|
uint256 const& ticketIndex,
|
||||||
beast::Journal j);
|
beast::Journal j);
|
||||||
|
|
||||||
|
static bool
|
||||||
|
useOldNumberRules(TxType txType);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TER
|
TER
|
||||||
apply();
|
apply();
|
||||||
|
|||||||
@@ -34,8 +34,33 @@ struct UnknownTxnType : std::exception
|
|||||||
// throw an "UnknownTxnType" exception on error
|
// throw an "UnknownTxnType" exception on error
|
||||||
template <class F>
|
template <class F>
|
||||||
auto
|
auto
|
||||||
with_txn_type(TxType txnType, F&& f)
|
with_txn_type(Rules const& rules, TxType txnType, F&& f)
|
||||||
{
|
{
|
||||||
|
// These global updates really should have been for every Transaction
|
||||||
|
// step: preflight, preclaim, calculateBaseFee, and doApply. Unfortunately,
|
||||||
|
// they were only included in doApply (via Transactor::operator()). That may
|
||||||
|
// have been sufficient when the changes were only related to operations
|
||||||
|
// that mutated data, but some features will now change how they read data,
|
||||||
|
// so these need to be more global.
|
||||||
|
//
|
||||||
|
// To prevent unintentional side effects on existing checks, they will be
|
||||||
|
// set for every operation only once SingleAssetVault (or later
|
||||||
|
// LendingProtocol) are enabled.
|
||||||
|
//
|
||||||
|
// See also Transactor::operator().
|
||||||
|
//
|
||||||
|
std::optional<NumberSO> stNumberSO;
|
||||||
|
std::optional<CurrentTransactionRulesGuard> rulesGuard;
|
||||||
|
if (rules.enabled(featureSingleAssetVault) /*|| rules.enabled(featureLendingProtocol)*/)
|
||||||
|
{
|
||||||
|
// raii classes for the current ledger rules.
|
||||||
|
// fixUniversalNumber predate the rulesGuard and should be replaced.
|
||||||
|
stNumberSO.emplace(rules.enabled(fixUniversalNumber));
|
||||||
|
rulesGuard.emplace(rules);
|
||||||
|
}
|
||||||
|
if (Transactor::useOldNumberRules(txnType))
|
||||||
|
Number::setMantissaScale(MantissaRange::small);
|
||||||
|
|
||||||
switch (txnType)
|
switch (txnType)
|
||||||
{
|
{
|
||||||
#pragma push_macro("TRANSACTION")
|
#pragma push_macro("TRANSACTION")
|
||||||
@@ -99,7 +124,7 @@ invoke_preflight(PreflightContext const& ctx)
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() {
|
return with_txn_type(ctx.rules, ctx.tx.getTxnType(), [&]<typename T>() {
|
||||||
auto const tec = Transactor::invokePreflight<T>(ctx);
|
auto const tec = Transactor::invokePreflight<T>(ctx);
|
||||||
return std::make_pair(
|
return std::make_pair(
|
||||||
tec,
|
tec,
|
||||||
@@ -126,50 +151,51 @@ invoke_preclaim(PreclaimContext const& ctx)
|
|||||||
{
|
{
|
||||||
// use name hiding to accomplish compile-time polymorphism of static
|
// use name hiding to accomplish compile-time polymorphism of static
|
||||||
// class functions for Transactor and derived classes.
|
// class functions for Transactor and derived classes.
|
||||||
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() -> TER {
|
return with_txn_type(
|
||||||
// preclaim functionality is divided into two sections:
|
ctx.view.rules(), ctx.tx.getTxnType(), [&]<typename T>() -> TER {
|
||||||
// 1. Up to and including the signature check: returns NotTEC.
|
// preclaim functionality is divided into two sections:
|
||||||
// All transaction checks before and including checkSign
|
// 1. Up to and including the signature check: returns NotTEC.
|
||||||
// MUST return NotTEC, or something more restrictive.
|
// All transaction checks before and including checkSign
|
||||||
// Allowing tec results in these steps risks theft or
|
// MUST return NotTEC, or something more restrictive.
|
||||||
// destruction of funds, as a fee will be charged before the
|
// Allowing tec results in these steps risks theft or
|
||||||
// signature is checked.
|
// destruction of funds, as a fee will be charged before the
|
||||||
// 2. After the signature check: returns TER.
|
// signature is checked.
|
||||||
|
// 2. After the signature check: returns TER.
|
||||||
|
|
||||||
// If the transactor requires a valid account and the
|
// If the transactor requires a valid account and the
|
||||||
// transaction doesn't list one, preflight will have already
|
// transaction doesn't list one, preflight will have already
|
||||||
// a flagged a failure.
|
// a flagged a failure.
|
||||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||||
|
|
||||||
if (id != beast::zero)
|
if (id != beast::zero)
|
||||||
{
|
{
|
||||||
if (NotTEC const preSigResult = [&]() -> NotTEC {
|
if (NotTEC const preSigResult = [&]() -> NotTEC {
|
||||||
if (NotTEC const result =
|
if (NotTEC const result =
|
||||||
T::checkSeqProxy(ctx.view, ctx.tx, ctx.j))
|
T::checkSeqProxy(ctx.view, ctx.tx, ctx.j))
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
if (NotTEC const result =
|
if (NotTEC const result =
|
||||||
T::checkPriorTxAndLastLedger(ctx))
|
T::checkPriorTxAndLastLedger(ctx))
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
if (NotTEC const result =
|
if (NotTEC const result =
|
||||||
T::checkPermission(ctx.view, ctx.tx))
|
T::checkPermission(ctx.view, ctx.tx))
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
if (NotTEC const result = T::checkSign(ctx))
|
if (NotTEC const result = T::checkSign(ctx))
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}())
|
}())
|
||||||
return preSigResult;
|
return preSigResult;
|
||||||
|
|
||||||
if (TER const result =
|
if (TER const result = T::checkFee(
|
||||||
T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)))
|
ctx, calculateBaseFee(ctx.view, ctx.tx)))
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return T::preclaim(ctx);
|
return T::preclaim(ctx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (UnknownTxnType const& e)
|
catch (UnknownTxnType const& e)
|
||||||
{
|
{
|
||||||
@@ -204,7 +230,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return with_txn_type(tx.getTxnType(), [&]<typename T>() {
|
return with_txn_type(view.rules(), tx.getTxnType(), [&]<typename T>() {
|
||||||
return T::calculateBaseFee(view, tx);
|
return T::calculateBaseFee(view, tx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -264,10 +290,11 @@ invoke_apply(ApplyContext& ctx)
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() {
|
return with_txn_type(
|
||||||
T p(ctx);
|
ctx.view().rules(), ctx.tx.getTxnType(), [&]<typename T>() {
|
||||||
return p();
|
T p(ctx);
|
||||||
});
|
return p();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (UnknownTxnType const& e)
|
catch (UnknownTxnType const& e)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user